Summary
Same defect family as #825 and #688: the adapter finishes an agent turn but never returns the JSON-RPC result (PromptResponse) for the in-flight session/prompt. The session sits "running" from the client's perspective until the client gives up and sends session/cancel.
This report adds a clean, minimal reproduction that pins down one detail the earlier threads left fuzzy: the adapter also withholds the end-of-turn usage_update (the cost-populated accounting frame), and only emits it when poked by session/cancel. In other words, at cancel time the adapter clearly still holds a fully-finished turn (it can produce the final cumulative usage with cost the instant it is asked to cancel), it just never surfaced the turn end on its own.
Still reproduces on @agentclientprotocol/claude-agent-acp@0.59.0 (packaging @anthropic-ai/claude-agent-sdk@0.3.207), with CLAUDE_ENABLE_STREAM_WATCHDOG default-on. Follows up on the closed #825 (maintainer asked to be pinged on recurrence) and #688.
Reproduction
Normal agent turn, no slash command, no async sub-agent. The model ran a couple of read-only Bash/Terminal tool calls, then ended its turn. The client (agent-of-empires) records every session/update and synthesizes a Stopped event once the awaited session/prompt future resolves. Client-side event log plus the client's own tracing for one session:
11:22:06.9 seq 8364 tool_call_completed (last tool: read files, success)
11:22:09.7 seq 8365 usage_update cost:null (mid-turn accounting frame)
--- 120 s of total silence: no session/update of any kind ---
11:24:09.261 client silent-turn watchdog fires -> sends session/cancel
11:24:09.273 seq 8366 usage_update cost:$13.70 (final cumulative usage, arrives only now)
11:24:09.273 seq 8367 turn resolves (PromptResponse finally delivered as cancelled)
The two things worth noting:
- Between
11:22:09 and 11:24:09 the adapter emitted nothing: no agent_message_chunk, no tool_call, no usage_update, and crucially no PromptResponse. The turn was over (the model chose to end after the tool reads) but the adapter never signaled it.
- The cost-populated
usage_update (8366) and the PromptResponse (8367) both landed in the same ~12 ms window, immediately after session/cancel. The cancel did not interrupt in-flight work; there was no in-flight work. It flushed a turn that had already completed.
Because the client only cancelled after a 120 s grace, the session showed "running" for two minutes after the agent had actually finished, then flipped to done. This is the user-visible symptom that led here.
Why this points at the adapter, not the model
The final cumulative usage frame carrying a non-null cost is the adapter's own end-of-turn accounting marker. That it can be produced the instant a cancel arrives means the turn's model work had genuinely finished; the adapter was holding a completed turn without surfacing it. So this is not a dropped/abnormal model stream (the #825 primary trigger) but a plain normal-turn end that the adapter failed to finalize, matching #688's "normal agent turns" shape.
Impact
Every occurrence wedges the session for the full client grace window (120 s here) and then can only be resolved by the client force-cancelling, which is indistinguishable at the protocol level from a real cancellation. A client that trusts the protocol would hang indefinitely; clients that add a watchdog pay the grace latency on every occurrence and lose the ability to tell a clean completion from a forced one. Observed frequency on our side: several times per day across sessions.
Environment
Related
Summary
Same defect family as #825 and #688: the adapter finishes an agent turn but never returns the JSON-RPC
result(PromptResponse) for the in-flightsession/prompt. The session sits "running" from the client's perspective until the client gives up and sendssession/cancel.This report adds a clean, minimal reproduction that pins down one detail the earlier threads left fuzzy: the adapter also withholds the end-of-turn
usage_update(the cost-populated accounting frame), and only emits it when poked bysession/cancel. In other words, at cancel time the adapter clearly still holds a fully-finished turn (it can produce the final cumulative usage with cost the instant it is asked to cancel), it just never surfaced the turn end on its own.Still reproduces on
@agentclientprotocol/claude-agent-acp@0.59.0(packaging@anthropic-ai/claude-agent-sdk@0.3.207), withCLAUDE_ENABLE_STREAM_WATCHDOGdefault-on. Follows up on the closed #825 (maintainer asked to be pinged on recurrence) and #688.Reproduction
Normal agent turn, no slash command, no async sub-agent. The model ran a couple of read-only
Bash/Terminaltool calls, then ended its turn. The client (agent-of-empires) records everysession/updateand synthesizes aStoppedevent once the awaitedsession/promptfuture resolves. Client-side event log plus the client's own tracing for one session:The two things worth noting:
11:22:09and11:24:09the adapter emitted nothing: noagent_message_chunk, notool_call, nousage_update, and crucially noPromptResponse. The turn was over (the model chose to end after the tool reads) but the adapter never signaled it.usage_update(8366) and thePromptResponse(8367) both landed in the same ~12 ms window, immediately aftersession/cancel. The cancel did not interrupt in-flight work; there was no in-flight work. It flushed a turn that had already completed.Because the client only cancelled after a 120 s grace, the session showed "running" for two minutes after the agent had actually finished, then flipped to done. This is the user-visible symptom that led here.
Why this points at the adapter, not the model
The final cumulative usage frame carrying a non-null cost is the adapter's own end-of-turn accounting marker. That it can be produced the instant a cancel arrives means the turn's model work had genuinely finished; the adapter was holding a completed turn without surfacing it. So this is not a dropped/abnormal model stream (the #825 primary trigger) but a plain normal-turn end that the adapter failed to finalize, matching #688's "normal agent turns" shape.
Impact
Every occurrence wedges the session for the full client grace window (120 s here) and then can only be resolved by the client force-cancelling, which is indistinguishable at the protocol level from a real cancellation. A client that trusts the protocol would hang indefinitely; clients that add a watchdog pay the grace latency on every occurrence and lose the ability to tell a clean completion from a forced one. Observed frequency on our side: several times per day across sessions.
Environment
@agentclientprotocol/claude-agent-acp0.59.0@anthropic-ai/claude-agent-sdk0.3.207@agentclientprotocol/sdk1.2.1CLAUDE_ENABLE_STREAM_WATCHDOGdefault-on (present in the bundled sdk); did not recover this case, consistent with the Prompt loop never terminates a turn when the model stream ends abnormally; session hangs 'running' until the next prompt drains it (0.52.0, sdk 0.3.191) #825 thread.Related
PromptResponseoutcome on a normal, cleanly-finished turn.PromptResponsenever sent on normal agent turns; same shape, now with the added observation that the finalusage_updateis withheld alongside theresultand both flush on cancel.