[Bug] Remote Control session approval dropped, agent wedges indefinitely
Bug: Remote Control "Yes, allow all X during this session" approval is dropped; agent wedges indefinitely
Component: claude-code Remote Control / replBridgePermissionCallbacks
Version: 2.1.121 (host agent), Remote Control mobile client (recent)
Severity: High — agent gets stuck in an unrecoverable state until manually canceled via tmux/console; mobile-only operators lose progress and have no recovery path.
Summary
When a permission prompt is approved through Remote Control by selecting option 2 (the "Yes, allow all edits in <dir>/ during this session" / shift+tab path) instead of option 1 ("Yes" / one-time), the response from the RC client never resolves the agent's pending permission promise. The TUI spinner ticks indefinitely, the tool never executes, and the file/edit is never applied. The agent's session-state file continues to report status: "busy" while no actual work is happening.
Selecting option 1 ("Yes" / once) on the same prompt works correctly: the response lands, the tool executes, and the agent proceeds.
Reproduction
- Run claude-code on a host with
remoteControlAtStartup: true(e.g. inside a persistent tmux session via systemd). - From the mobile RC client, send a prompt that requires the agent to make a file edit (
Edit/Updatetool). - When the approval prompt renders in RC ("Do you want to make this edit to <file>?"):
- Negative case: tap option 2 ("Yes, allow all edits in <dir>/ during this session").
- Observe: spinner ticks past expected duration; no edit applied; file mtime unchanged.
- Cancel the wedge by sending
Escape Escapedirectly to the host's tmux pane (RC has no recovery affordance for this state). - Resume work; trigger the same edit again.
- Positive case: tap option 1 ("Yes" / one-time) on the second prompt. Observe: edit applies immediately, file mtime updates, agent proceeds.
Reproduced consistently in this environment across multiple distinct edit prompts in a single session.
Evidence
- File mtime before/after:
envelope.shwas sized 2805 bytes, mtime Apr 26.- After option 2 approval: still 2805 bytes, still Apr 26 (edit not applied) — wedge persisted >25 min in one observed instance with the spinner reading
Fixing… (25m 39s · ↓ 1.6k tokens). - After Esc-Esc cancel + re-prompt + option 1 approval: 3392 bytes, mtime Apr 30 10:05 (edit applied).
- Session state:
~/.claude/sessions/<pid>.jsonreportsstatus: "busy"continuously during the wedge, withupdatedAtticking. Indicates the TUI/process knows it's awaiting a response that never arrives. - Bridge protocol: from the bundled JS source visible in
/tmp/claude-tui.log, the permission flow usesreplBridgePermissionCallbacks.sendRequest(reqId, host, ...)and a UUID-keyedonResponselistener. The wedge is consistent with the option-2 response failing to invoke the registered listener. - Trace evidence (tmux pipe-pane log): the prompt renders correctly with both options; the post-approval transition only fires for option 1. For option 2 the spinner re-renders the same task title with no new tool dispatch and no new tokens.
- Direct-keystroke test during wedge: sending
1+Enterto the host tmux pane while the spinner is wedged on an option-2 selection has no effect — the TUI is no longer rendering the prompt, so there is no input target. This rules out the prompt simply being scrolled off; the prompt-receiving widget itself has been torn down while the awaited promise is still alive. - Recovery: only
Escape Escape(cancel current tool-use) clears the wedge. The pending edit is discarded; the agent must be re-prompted to retry.
State of RC client during wedge
The RC mobile client's view of the conversation stops updating once the option-2 wedge occurs — even though the host is still running, the session is busy, and the watchdog/tmux interventions on the host side are advancing the local TUI state. This suggests the same bridge channel that drops the approval response also stops streaming subsequent state events to RC, leaving the mobile UI frozen at the wedge moment.
Settings (relevant excerpt)
{
"permissions": {
"allow": [
"Bash(pct list:*)",
"Bash(qm list:*)",
"Bash(blkid /dev/sdd1)",
"Bash(lsblk /dev/sdd)"
],
"defaultMode": "default",
"additionalDirectories": ["/tmp"]
},
"remoteControlAtStartup": true,
"agentPushNotifEnabled": true,
"editorMode": "vim",
"hooks": {
"PreToolUse": [
{ "matcher": "Bash",
"hooks": [{ "type": "command", "command": ".../pretooluse-bash-timeout.sh" }] }
]
}
}
No settings.local.json override. No permission hooks. No allow-list entry for Edit/Update/Write tools (so every edit triggers the prompt — which is desired).
Workaround
Only use option 1 ("Yes") when approving via Remote Control. Avoid option 2 / shift+tab entirely until fixed. If option 2 is clicked accidentally, the only recovery is shell access to the host's tmux session to send Escape Escape.
Suggested fix direction
Likely candidates without source access:
- The reqId used for the option-2 response code path differs from the one the listener is registered under (off-by-one or different UUID generator on the "session-scoped" branch).
- The
behaviorfield carries an enum value (e.g."allow_session"or"allow_permanent") that the server-side dispatcher doesn't map to a resolution call — only"allow"and"deny"are wired up. - A premature
cancelRequestor listener cleanup on the session-scoped path tears down the resolver before the response arrives.
Environment
- Host: Debian 12, claude-code 2.1.121 launched via systemd inside a persistent tmux session (
claude-session.service, ExecStart=tmux new-session -d -s claude /home/claude/.local/bin/claude --continue). - Remote Control: enabled at startup, paired with a mobile client over the network. Persistent sessions via
--continue. - No proxy / sandbox / unusual auth; standard OAuth.
Impact
For mobile-only operators (the entire RC use case), this turns "Yes, allow all" — an explicit, deliberate, "I trust this batch" affordance — into a session-killing trap. The natural reaction to a long batch of repeated edit prompts is to use option 2; doing so loses progress and requires console access to recover.
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗