Skip to content

Claude CLI subprocess death leaves session permanently broken #338

@ElleNajt

Description

@ElleNajt

Problem

When the underlying claude CLI subprocess dies (e.g., SIGTERM, exit code 143), claude-code-acp enters a permanently broken state:

  1. The ProcessTransport in the Claude Agent SDK detects the exit and sets ready = false
  2. The Query async generator finishes or errors
  3. But the session remains in this.sessions and claude-code-acp stays alive (due to process.stdin.resume())
  4. Any subsequent session/prompt requests on that session fail with ProcessTransport is not ready for writing
  5. The error surfaces as an unhandled rejection:
Error handling request {
  id: 6, jsonrpc: '2.0', method: 'session/prompt', ...
} {
  code: -32603,
  message: 'Internal error',
  data: { details: 'Claude Code process exited with code 143' }
}
Unhandled Rejection at: Promise {
  <rejected> Error: ProcessTransport is not ready for writing

The session is dead but claude-code-acp doesn't know it. There's no recovery path.

I'm seeing this frequently. I'm also running a multiplexing proxy in front of claude-code-acp (multiple ACP frontends sharing one agent), which may be contributing — but the core issue is that claude-code-acp has no recovery mechanism regardless of what's killing the subprocess.

Suggested fix

Each call to query() creates a new ProcessTransport and spawns a fresh claude CLI subprocess. The SDK already supports resume and continue options to pick up a conversation from its JSONL session file.

When prompt() detects that the underlying transport has died (e.g., query.next() returns done unexpectedly or throws a transport error), claude-code-acp could:

  1. Detect the transport death (distinguish it from normal cancellation/completion)
  2. Create a new query() with resume: sessionId to spawn a fresh CLI process that replays the conversation
  3. Update this.sessions[sessionId] with the new query/input
  4. Retry the prompt

This would make sessions resilient to transient CLI crashes without requiring the parent process or client to restart everything.


Written by Claude

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