Agent SDK bundled CLI stalls after final message: transcript has complete end_turn message, but stream-json result is never emitted
Environment
- macOS (darwin 25.5.0, arm64)
@anthropic-ai/claude-agent-sdk0.3.220 — the bundledclaude-agent-sdk-darwin-arm64/claudebinary is the process in question- Driven over ACP by
@agentclientprotocol/claude-agent-acp0.66.x (spec^0.66.0), host is a long-lived node harness - Invocation (from
ps):claude --output-format stream-json --verbose --input-format stream-json … - For version context: the standalone Claude Code CLI on the same machine is 2.1.233
What happened
A long-lived interactive session (~2.5 h, many successful turns). One turn then stalled at the very end — after the model had finished writing its answer. Timestamps UTC, 2026-08-17:
- 03:23:31 — user turn starts
- 03:23:33 — assistant
tool_use(Bash) appears in the session transcript (~/.claude/projects/…/<session>.jsonl) - 03:23:49 —
tool_resultappears - 03:24:15 — assistant
thinkingblock appears - 03:24:50 — final assistant text is written to the transcript: a complete message with
stop_reason: "end_turn" - after that: no
resultevent is ever emitted on the stream-json output; no further transcript writes, no stderr output, no exit
Process state (sampled ~25 min into the stall)
| process | state |
|---|---|
| bundled claude | alive, blocked in sleep; ~4:24 CPU consumed during the session, 0% since the stall |
| ACP adapter / host | alive, event-loop idle (uv__io_poll) |
The client's session/prompt promise therefore hangs forever (our client had no timeout of its own — we have since added a watchdog on our side).
Expected
After the final assistant message, a result event — or an error, or a process exit. Anything but indefinite silence: the transcript says the turn completed, the stream never does.
Reproducibility
One occurrence in normal use; not reproducible on demand. Happy to provide redacted transcript / status logs on request.
Workaround
Client-side stall watchdog + session/cancel escape hatch; the session itself is recoverable losslessly with claude --resume <session-id> (the transcript survives intact, including the final message that never made it onto the stream).