[BUG] [bridge:repl] Failed to parse ingress message: 'H.rules.length' undefined when granting "Allow for session" via remote-control

Resolved 💬 3 comments Opened Apr 30, 2026 by BorisScheiber Closed May 4, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Summary

When claude --remote-control is connected to from the mobile / web client
(claude.ai/code), granting Bash permission with "Allow for this session" causes the
bridge:repl ingress parser to throw, after which the originating tool call never
proceeds and any subsequent tool calls in the same turn queue forever. Choosing
"Allow once" for the identical Bash command works correctly.

The bug is reliably reproducible and 100% deterministic on a fresh service start.

## Environment

  • Claude Code: 2.1.123 (native binary install at

~/.local/share/claude/versions/2.1.123)

  • OS: Ubuntu 24.04, Linux 6.8 (x86_64)
  • Node: bundled with native install
  • Running headless under systemd (Type=forking) inside a detached `tmux new-session

-d`

  • Mobile client: claude.ai/code via mobile browser (Android)
  • Auth: OAuth (Anthropic subscription)

ExecStart used during diagnosis (debug logging enabled):
``
/usr/bin/tmux new-session -d -s claude-remote -c <project-dir> \
/path/to/claude --remote-control --debug --debug-file /tmp/claude-remote-debug.log
``

## Reproducer

  1. Start claude --remote-control on a Linux server (under systemd + detached tmux

works; we did not test other configs).

  1. Connect to the running session from the mobile client at claude.ai/code.
  2. Send any prompt that causes the model to call the Bash tool (e.g. "give me a server

status update").

  1. When the permission dialog appears on the phone, tap "Allow for this session".

Result: tool call shows Running… indefinitely. Any further Bash calls in the same
turn show Waiting… and never start.

If step 4 is changed to "Allow once" (everything else identical), the Bash call
succeeds and returns output.

## Process-level observation

While a tool call is hung after "Allow for session":

  • The claude --remote-control process has zero child processes (verified with `pstree

-p and pgrep --ppid`).

  • Despite that, the process holds open file descriptors to a PTY pair: `fd 5 →

/dev/ptmx (master) and fd 8 → /dev/pts/N` (slave). This suggests the runtime
allocates a PTY before attempting to fork/spawn the bash subprocess, then aborts
before the spawn — leaving the PTY fds dangling.

  • The main thread is parked in do_poll (per /proc/<pid>/wchan). No CPU activity, no

further log output beyond the parse-error line.

## Debug log evidence

### Failure path — "Allow for session"

``
[DEBUG] Permission suggestions for Bash: [
{ "type": "addRules",
"rules": [{ "toolName": "Bash", "ruleContent": "sudo systemctl *" }],
"behavior": "allow",
"destination": "localSettings" } ]
[DEBUG] executePermissionRequestHooks called for tool: Bash
[DEBUG] [remote-bridge] Sent control_request request_id=...
[DEBUG] [remote-bridge] Sending 1 message(s)
... (user taps "Allow for this session" on phone)
[DEBUG] SSETransport: Event seq=N event_type=control_response
payload_type=control_response
[DEBUG] [bridge:repl] Ingress message type=control_response
[DEBUG] Persisting permission update: addRules to source 'localSettings'
[DEBUG] [bridge:repl] Failed to parse ingress message:
undefined is not an object (evaluating 'H.rules.length')
``

After the parse error: no further events for that tool call. The originally requested
## Debug log evidence

### Failure path — "Allow for session"

``
[DEBUG] Permission suggestions for Bash: [
{ "type": "addRules",
"rules": [{ "toolName": "Bash", "ruleContent": "sudo systemctl *" }],
"behavior": "allow",
"destination": "localSettings" } ]
[DEBUG] executePermissionRequestHooks called for tool: Bash
[DEBUG] [remote-bridge] Sent control_request request_id=...
[DEBUG] [remote-bridge] Sending 1 message(s)
... (user taps "Allow for this session" on phone)
[DEBUG] SSETransport: Event seq=N event_type=control_response payload_type=control_response
[DEBUG] [bridge:repl] Ingress message type=control_response
[DEBUG] Persisting permission update: addRules to source 'localSettings'
[DEBUG] [bridge:repl] Failed to parse ingress message:
undefined is not an object (evaluating 'H.rules.length')
``

After the parse error: no further events for that tool call. The originally requested rule ({toolName: "Bash", ruleContent: "sudo systemctl *", behavior: "allow"}) does get written to ~/.claude/settings.local.json
i.e. the persistence side-effect succeeds, only the post-persist parsing of the same control_response throws.

### Success path — "Allow once" (identical Bash command, same session)

``
[DEBUG] Permission suggestions for Bash: [...]
[DEBUG] executePermissionRequestHooks called for tool: Bash
[DEBUG] SSETransport: Event seq=N event_type=control_response payload_type=control_response
[DEBUG] [bridge:repl] Ingress message type=control_response
<no "Persisting permission update" line>
<no parse error>
<Bash subprocess spawned, output streams back, tool call completes>
``

So the divergence is:

  • "Allow once" → control_response without addRules payload → parser handles it cleanly.
  • "Allow for session" → control_response with addRules payload → Persisting permission update succeeds, then a second consumer of the same payload throws on H.rules.length.

In a single --remote-control lifetime I reproduced this 3/3 times for "Allow for session" and 0/6 for "Allow once".

## Hypothesis

The control_response message that carries an addRules payload appears to be consumed twice in the bridge:repl path: once by the permission-persist branch (works) and once by what looks like a different parser branch that
expects the message body to have a .rules property which is in fact undefined at that point. The minified identifier H.rules.length and the wording "Failed to parse ingress message" suggest the second consumer is a generic
ingress dispatch / shape validator that wasn't updated when the addRules schema landed (or vice versa).

Whatever the exact cause, the failure is fatal to the in-flight tool call — execution never proceeds past executePermissionRequestHooks, the PTY fds leak, and the runtime gets stuck waiting for an event that will never
arrive.

## Workarounds

  • Always tap "Allow once" on the phone (works, but unworkable for routine use).
  • Start the service with --permission-mode bypassPermissions — skips the entire permission round-trip, no parse path is hit.
  • Pre-populate .claude/settings.json / .claude/settings.local.json allow-rules so the model never triggers a permission prompt for the commands you expect to use.

## Impact

For users running claude --remote-control headlessly to interact via mobile, the natural "Allow for session" choice silently breaks every tool call after the first. The on-screen state ("Running…" forever) gives no hint that
the underlying bridge has errored — only --debug output reveals it.

What Should Happen?

When a user grants Bash permission via "Allow for this session" through the mobile / web client, the runtime should:

  1. Persist the rule to the configured settings source (this part already works correctly).
  2. Cleanly resolve the in-flight permission request and proceed to spawn the Bash subprocess.
  3. Stream the command output back to the client and mark the tool call as completed.

The "Allow for this session" path should be functionally equivalent to "Allow once" in terms of the requested Bash call going through — the only difference being that the rule is also persisted for the remainder of the
session/forever, so subsequent matching calls don't re-prompt.

In other words: choosing the more permissive option should never produce a worse outcome than choosing the less permissive one.

Error Messages/Logs

Failure trace (from --debug log) when "Allow for this session" is tapped on the phone:                                                                                                                                             
                                                                                                                                                                                                                                     
  [DEBUG] Permission suggestions for Bash: [                                                                                                                                                                                         
    { "type": "addRules",                                                                                                                                                                                                            
      "rules": [{ "toolName": "Bash", "ruleContent": "sudo systemctl *" }],
      "behavior": "allow",                                                                                                                                                                                                           
      "destination": "localSettings" } ]
  [DEBUG] executePermissionRequestHooks called for tool: Bash                                                                                                                                                                        
  [DEBUG] [remote-bridge] Sent control_request request_id=<id>                                                                                                                                                                       
  [DEBUG] SSETransport: Event seq=N event_type=control_response payload_type=control_response
  [DEBUG] [bridge:repl] Ingress message type=control_response                                                                                                                                                                        
  [DEBUG] Persisting permission update: addRules to source 'localSettings'
  [DEBUG] [bridge:repl] Failed to parse ingress message: undefined is not an object (evaluating 'H.rules.length')                                                                                                                    
                                                                                                                                                                                                                                     
  After this line, no further log activity for the in-flight tool call. The persisted rule does land in ~/.claude/settings.local.json correctly — only the post-persist parsing throws.                                              
                                                                                                                                                                                                                                     
  For comparison, the "Allow once" path (which works) produces no "Persisting permission update" line and no parse error:                                                                                                            
                  
  [DEBUG] Permission suggestions for Bash: [...]                                                                                                                                                                                     
  [DEBUG] executePermissionRequestHooks called for tool: Bash
  [DEBUG] SSETransport: Event seq=N event_type=control_response payload_type=control_response
  [DEBUG] [bridge:repl] Ingress message type=control_response                                                                                                                                                                        
  <Bash subprocess spawns, output returns, tool call completes>                                                                                                                                                                      
                                                                                                                                                                                                                                     
  Process-level snapshot while the tool call is hung:                                                                                                                                                                                
  - Main thread parked in do_poll (via /proc/<pid>/wchan)
  - Zero child processes (pstree -p shows no bash/sh)                                                                                                                                                                                
  - A PTY pair is held open: fd 5 → /dev/ptmx, fd 8 → /dev/pts/N (allocated before the spawn that never happens)                                                                                                                     
                                                                                                                                                                                                                                     
  Reproducibility in one --remote-control lifetime: 3/3 hangs on "Allow for session", 0/6 on "Allow once".

Steps to Reproduce

  1. On a Linux machine, start a headless claude --remote-control session. Easiest reproducer is via systemd + detached tmux:

/etc/systemd/system/claude-remote.service:
[Service]
Type=forking
User=<your-user>
Environment=HOME=/home/<your-user>
ExecStart=/usr/bin/tmux new-session -d -s claude-remote -c <project-dir> /path/to/claude --remote-control --debug --debug-file /tmp/claude-remote-debug.log
ExecStop=/usr/bin/tmux kill-session -t claude-remote
Restart=on-failure

Then: sudo systemctl daemon-reload && sudo systemctl restart claude-remote

(A plain interactive claude --remote-control started in a normal tmux pane should reproduce equally well — the systemd setup just makes it easier to start clean.)

  1. Connect to the running remote-control session from the mobile client at claude.ai/code (Android browser in my case; haven't tested iOS).
  1. Send any prompt that causes the model to call the Bash tool. A reliable trigger is "give me a server status update" — the model will reach for systemctl is-active … or similar.
  1. When the permission dialog appears on the phone, tap "Allow for this session".

Expected: Bash runs, output streams back.
Actual: The tool call shows "Running…" indefinitely. Any subsequent Bash tool calls in the same model turn show "Waiting…" and also never run. The model's text response (around the tool calls) is never finalized.

  1. To confirm it's specific to the "Allow for session" path, kill the session, restart it fresh, repeat steps 2-3, but in step 4 tap "Allow once" instead. The same Bash command will execute and return output normally.
  1. (Optional, for inspection) On the server, while a call is hung:
  • cat /proc/<pid>/wchan → "do_poll"
  • pstree -p <pid> → no children
  • ls -l /proc/<pid>/fd → fd 5 = /dev/ptmx, fd 8 = /dev/pts/N
  • tail /tmp/claude-remote-debug.log → ends with the "Failed to parse ingress message: ... 'H.rules.length'" line

No project-specific code or files are required; the bug reproduces with any prompt that causes a Bash tool call. There is no permission allow-list configured in ~/.claude/settings.json or .claude/settings.json — the prompt
appears because the bash command is not pre-allowed.

Claude Model

Opus

Is this a regression?

Yes, this worked in a previous version

Last Working Version

_No response_

Claude Code Version

2.1.123 (Claude Code)

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Non-interactive/CI environment

Additional Information

_No response_

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗