Bug Description
Commit 7f1c6c2 ("Use idle session state as end of turn (#463)") and its follow-up 23b3073 ("Cleanup based on new idle state #463 (#480)") moved the prompt loop's exit from the result message handler to session_state_changed(idle). This relies on the Claude Code binary supporting the CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS env var.
When the Claude Code binary does not support this feature (e.g., older versions), session_state_changed(idle) is never emitted. The prompt loop calls session.query.next() after processing the result message and blocks forever, because the process is still alive but sends no further messages.
This causes the JSON-RPC response for session/prompt to never be sent, and the client's conn.prompt() future never resolves — deadlocking the client.
Steps to Reproduce
- Use
claude-agent-acp v0.23.0+ (which includes commit 7f1c6c2)
- Connect with a Claude Code binary that does not support
CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS (e.g., Claude Code 2.1.77 installed by npm, set env CLAUDE_CODE_EXECUTABLE to point to it)
- Send a
session/prompt request
- The agent processes the turn, sends streaming notifications, receives
result(success) — but never returns the PromptResponse
- Client hangs indefinitely
Root Cause
In the prompt loop (acp-agent.ts:prompt()), all result subtypes (success, error_during_execution, error_max_*) now set stopReason and break instead of return. The only return path is via session_state_changed(idle), which requires the env var to be supported by the binary:
// Result handler — no longer returns
case "success": {
// ... error checks ...
break; // was: return { stopReason: "end_turn", usage }
}
// Only exit path
case "session_state_changed": {
if (message.state === "idle") {
return { stopReason, usage: sessionUsage(session) }; // never reached
}
}
After break, the loop calls await session.query.next() which blocks forever since the process sends no more messages.
Expected Behavior
The prompt loop should return a PromptResponse even when session_state_changed is not available.
Environment
- claude-agent-acp: v0.23.0+ (commits 7f1c6c2, 23b3073)
- Claude Code binary: any version that doesn't support
CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS
Bug Description
Commit 7f1c6c2 ("Use idle session state as end of turn (#463)") and its follow-up 23b3073 ("Cleanup based on new idle state #463 (#480)") moved the prompt loop's exit from the
resultmessage handler tosession_state_changed(idle). This relies on the Claude Code binary supporting theCLAUDE_CODE_EMIT_SESSION_STATE_EVENTSenv var.When the Claude Code binary does not support this feature (e.g., older versions),
session_state_changed(idle)is never emitted. The prompt loop callssession.query.next()after processing theresultmessage and blocks forever, because the process is still alive but sends no further messages.This causes the JSON-RPC response for
session/promptto never be sent, and the client'sconn.prompt()future never resolves — deadlocking the client.Steps to Reproduce
claude-agent-acpv0.23.0+ (which includes commit 7f1c6c2)CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS(e.g., Claude Code 2.1.77 installed by npm, set env CLAUDE_CODE_EXECUTABLE to point to it)session/promptrequestresult(success)— but never returns thePromptResponseRoot Cause
In the prompt loop (
acp-agent.ts:prompt()), allresultsubtypes (success,error_during_execution,error_max_*) now setstopReasonandbreakinstead ofreturn. The only return path is viasession_state_changed(idle), which requires the env var to be supported by the binary:After
break, the loop callsawait session.query.next()which blocks forever since the process sends no more messages.Expected Behavior
The prompt loop should return a
PromptResponseeven whensession_state_changedis not available.Environment
CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS