Summary
When Claude calls ExitPlanMode inside an ACP session, it returns <error>Exit plan mode?</error> and plan mode never exits. User text messages ("Proceed", "go go go") don't resolve the prompt — they start new conversation turns instead. Observed 6 consecutive failures in a single session.
EnterPlanMode works fine. Only ExitPlanMode is affected.
Reproduction
- Start an ACP session (any workflow)
- Ask Claude to plan a non-trivial task (triggers
EnterPlanMode)
- Claude writes a plan and calls
ExitPlanMode
- Tool returns
<error>Exit plan mode?</error>
- Send any text message — plan mode remains active, edits blocked
- Claude calls
ExitPlanMode again — same result, infinite loop
Root Cause
The ACP runner uses a pre-approval security model:
- Permission mode: Hardcoded to
acceptEdits (bridge.py line 742)
- Tool allowlist:
DEFAULT_ALLOWED_TOOLS in mcp.py lines 26-37
ExitPlanMode is not in the allowlist. When Claude calls it, the SDK generates a permission/approval prompt. The runner has zero permission prompt handling code — it relies entirely on the allowlist to prevent prompts. The unhandled prompt surfaces as <error>Exit plan mode?</error>.
Beyond basic tool permissions, ExitPlanMode also includes a plan approval step — in CLI mode this shows a confirmation dialog where the user accepts or rejects the plan. In ACP sessions, this dialog has no handler.
Impact
- Blocks any workflow that uses plan mode (spec-kit, complex multi-file tasks)
- Claude gets stuck in read-only mode and cannot make any edits
- Users must abandon the session or avoid plan mode entirely
- All tools except Bash and Read are blocked (Write, Edit fail with "Cannot write while in plan mode")
Recommended Fix: Handle plan approval in the runner
Adding ExitPlanMode to DEFAULT_ALLOWED_TOOLS may not be sufficient — the tool has a separate plan approval callback beyond basic tool permissions.
The runner should handle plan approval directly:
- Detect pending plan approval prompts from the SDK (likely exposed as a callback or event)
- Auto-approve — the plan is already visible in the conversation message stream, so the user reviews it there, not via a CLI dialog
- If the user wants changes, they say so in the next message (which is how it already works in practice)
This approach:
- Fixes
ExitPlanMode completely (both permission and approval)
- Future-proofs against other interactive prompts the SDK might add
- Matches ACP's existing UX model where the conversation stream IS the review surface
Other tools potentially affected
Any tool not in DEFAULT_ALLOWED_TOOLS could hit the same unhandled-prompt issue:
EnterPlanMode, ExitPlanMode
NotebookEdit
CronCreate / CronDelete
EnterWorktree / ExitWorktree
ScheduleWakeup
Worth auditing the full list of Claude Code tools against DEFAULT_ALLOWED_TOOLS.
Key Files
| File |
What |
components/runners/ambient-runner/ambient_runner/bridges/claude/mcp.py L26-37 |
DEFAULT_ALLOWED_TOOLS — missing ExitPlanMode |
components/runners/ambient-runner/ambient_runner/bridges/claude/bridge.py L742 |
permission_mode: "acceptEdits" hardcoded |
components/runners/ambient-runner/ambient_runner/bridges/claude/session.py L118-131 |
SDK client creation — no approval handler |
components/runners/ambient-runner/ambient_runner/observability_models.py L53-54 |
ExitPlanMode metric constant (tracking only) |
🤖 Generated with Claude Code
Summary
When Claude calls
ExitPlanModeinside an ACP session, it returns<error>Exit plan mode?</error>and plan mode never exits. User text messages ("Proceed", "go go go") don't resolve the prompt — they start new conversation turns instead. Observed 6 consecutive failures in a single session.EnterPlanModeworks fine. OnlyExitPlanModeis affected.Reproduction
EnterPlanMode)ExitPlanMode<error>Exit plan mode?</error>ExitPlanModeagain — same result, infinite loopRoot Cause
The ACP runner uses a pre-approval security model:
acceptEdits(bridge.pyline 742)DEFAULT_ALLOWED_TOOLSinmcp.pylines 26-37ExitPlanModeis not in the allowlist. When Claude calls it, the SDK generates a permission/approval prompt. The runner has zero permission prompt handling code — it relies entirely on the allowlist to prevent prompts. The unhandled prompt surfaces as<error>Exit plan mode?</error>.Beyond basic tool permissions,
ExitPlanModealso includes a plan approval step — in CLI mode this shows a confirmation dialog where the user accepts or rejects the plan. In ACP sessions, this dialog has no handler.Impact
Recommended Fix: Handle plan approval in the runner
Adding
ExitPlanModetoDEFAULT_ALLOWED_TOOLSmay not be sufficient — the tool has a separate plan approval callback beyond basic tool permissions.The runner should handle plan approval directly:
This approach:
ExitPlanModecompletely (both permission and approval)Other tools potentially affected
Any tool not in
DEFAULT_ALLOWED_TOOLScould hit the same unhandled-prompt issue:EnterPlanMode,ExitPlanModeNotebookEditCronCreate/CronDeleteEnterWorktree/ExitWorktreeScheduleWakeupWorth auditing the full list of Claude Code tools against
DEFAULT_ALLOWED_TOOLS.Key Files
components/runners/ambient-runner/ambient_runner/bridges/claude/mcp.pyL26-37DEFAULT_ALLOWED_TOOLS— missingExitPlanModecomponents/runners/ambient-runner/ambient_runner/bridges/claude/bridge.pyL742permission_mode: "acceptEdits"hardcodedcomponents/runners/ambient-runner/ambient_runner/bridges/claude/session.pyL118-131components/runners/ambient-runner/ambient_runner/observability_models.pyL53-54ExitPlanModemetric constant (tracking only)🤖 Generated with Claude Code