Skip to content

prompt() hangs when Claude Code binary doesn't support CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS #497

@vincentye38

Description

@vincentye38

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

  1. Use claude-agent-acp v0.23.0+ (which includes commit 7f1c6c2)
  2. 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)
  3. Send a session/prompt request
  4. The agent processes the turn, sends streaming notifications, receives result(success) — but never returns the PromptResponse
  5. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions