ExitPlanMode approval via --permission-prompt-tool stdio silently ignored — process hangs

Resolved 💬 3 comments Opened Mar 27, 2026 by nathanschram Closed May 4, 2026

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

  1. 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
``

  1. Send a prompt that triggers plan mode and ExitPlanMode
  1. Wait for the control_request on stdout:

``json
{"type":"control_request","request_id":"733fbf9e-...","request":{"subtype":"can_use_tool","tool_name":"ExitPlanMode","input":{}}}
``

  1. Write an approval to stdin:

``json
{"type":"control_response","response":{"subtype":"success","request_id":"733fbf9e-...","response":{"behavior":"allow","updatedInput":{}}}}
``

  1. Expected: Claude Code emits a tool result event and continues
  2. 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())

View original on GitHub ↗

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