ExitPlanMode approval via --permission-prompt-tool stdio silently ignored — process hangs
Preflight Checklist
- [x] I have searched existing issues and this has not been reported
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code
What's Wrong?
When running Claude Code with --permission-prompt-tool stdio and --permission-mode plan, an ExitPlanMode approval sent as a control_response via stdin is silently ignored. The process continues running (10% CPU, 387MB RSS) but stops emitting JSONL events on stdout, effectively hanging.
The approval payload is valid JSON, written successfully to the stdin pipe (no write errors), but Claude Code does not process it — no tool result event, no subsequent assistant output, nothing. The process stays alive indefinitely.
Steps to Reproduce
- Start Claude Code:
``bash``
claude --output-format stream-json --input-format stream-json --verbose \
--allowedTools Bash,Read,Edit,Write --permission-mode plan \
--permission-prompt-tool stdio
- Send a prompt that triggers plan mode and ExitPlanMode
- Wait for the
control_requeston stdout:
``json``
{"type":"control_request","request_id":"733fbf9e-...","request":{"subtype":"can_use_tool","tool_name":"ExitPlanMode","input":{}}}
- Write an approval to stdin:
``json``
{"type":"control_response","response":{"subtype":"success","request_id":"733fbf9e-...","response":{"behavior":"allow","updatedInput":{}}}}
- Expected: Claude Code emits a tool result event and continues
- Actual: No further JSONL output. Process stays alive at ~10% CPU. stdout is silent.
Evidence
From a production session (v2.1.85, Linux 6.8.0, session 05028bc9):
12:35:03 — Session started (PID 513789)
12:39:20 — control_request emitted for ExitPlanMode (request_id=733fbf9e)
12:39:38 — control_response written to stdin pipe (approved=true, no write error)
... silence — no JSONL events on stdout ...
12:54:49 — Liveness watchdog: idle_seconds=600.1, last_event_type=control_request
Process still alive: 10.1% CPU, 387MB RSS, 25 TCP connections
The approval was written using anyio.abc.ByteSendStream.send() on the subprocess stdin pipe. The write completed without error. Claude Code's event stream (--output-format stream-json) produced no further events after the control_request.
The wrapper eventually sent a timeout deny for the same request_id after 5 minutes. It is unknown whether Claude Code processed the original approve, the subsequent deny, or neither.
Workaround
Our wrapper (Untether) implements a 5-minute auto-deny timeout that eventually unblocks the subprocess, but this causes the plan to be rejected rather than approved. We've added reconciliation logic to prevent sending conflicting responses (approve then deny for the same request_id) in littlebearapps/untether#229.
Possibly Related
- #33747 — Plan mode freezes after approval until second instance is opened (closed as dupe of #33273, locked)
- #33273 — ExitPlanMode with "clear context" treated as rejection (closed, locked)
- #25629 — CLI hangs indefinitely after sending result event in stream-json mode (closed)
- #29712 — Plan mode hangs with pause on SteamOS with multiple MCP servers (open)
- #37482 — MCP stdio servers lose stdin pipe after ~10-20 minutes (open)
Is this a regression?
Unknown — this is the first occurrence we've observed across hundreds of sessions using this integration. It may be a transient race condition.
Claude Code Version
2.1.85
Claude Model
claude-opus-4-6 (1M context)
Platform
Anthropic API (Max subscription)
Operating System
Linux 6.8.0-106-generic (Ubuntu, Hetzner VPS)
Terminal/Shell
Non-interactive subprocess (no TTY attached to stdout/stderr, PTY on stdin via pty.openpty())
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗