Files
open-design/e2e/lib
lefarcenandGitHub 6a10e8e178 fix(daemon): classify Claude mid-stream socket drops as retryable (#3937)
* fix(daemon): classify Claude mid-stream socket drops as retryable

When the Claude Code CLI's connection to the Anthropic API (or a custom
ANTHROPIC_BASE_URL relay) drops mid-response, the SDK surfaces 'The socket
connection was closed unexpectedly...'. The daemon relayed this raw SDK
string verbatim into the error bubble because diagnoseClaudeCliFailure had
no branch for an established-then-dropped connection (distinct from the
existing connection-refused case).

Add a connection-drop branch that classifies socket-close / ECONNRESET /
socket hang up / fetch failed / premature close into a human-readable,
retryable diagnostic, pointing at the proxy when a custom endpoint is set
and at the network otherwise. Most visible on long, large generations.

The fake-agent CLI gains a 'Return a daemon socket-drop failure' mode whose
claude shape was captured from the real Claude Code CLI 2.1.168 driven at a
socket-dropping endpoint (error on stdout as a synthetic assistant block +
result{is_error:true}, exit 1, empty stderr), so the e2e path exercises the
real failure shape rather than a guessed one.

* fix(daemon): use OpenClaude socket-drop diagnostics

Generated-By: looper 0.9.7 (runner=fixer, agent=opencode)

* fix(daemon): harden Claude connection-drop diagnostic from real repro

Reproduced end-to-end against the real Claude Code CLI (2.1.168, official
auth, no ANTHROPIC_BASE_URL) by tunneling through a local proxy that resets
the TLS stream mid-response. Two real error faces observed: 'The socket
connection was closed unexpectedly' (SSE cut) and 'Unable to connect to API
(ECONNRESET)' (tunnel reset); the CLI retries internally for minutes before
surfacing either, which is why long runs appear to abort after a while.

Broaden the connection-drop matcher with 'unable to connect to api' and
ETIMEDOUT, and lock the real ECONNRESET transcript into the test suite.

* fix(daemon): narrow Claude API drop matcher

Generated-By: looper 0.9.7 (runner=fixer, agent=opencode)

* feat(daemon,web): structured AGENT_CONNECTION_DROPPED code + localized message

Promote the Claude connection-drop diagnostic from a raw English string to a
stable, machine-readable failure class so the web can localize it and triage
can count it by code.

- contracts: add AGENT_CONNECTION_DROPPED to API_ERROR_CODES.
- daemon: ClaudeCliDiagnostic carries an optional code; the connection-drop
  branches stamp AGENT_CONNECTION_DROPPED; server.ts prefers diagnostic.code
  over the generic service-failure sniff so the run records the specific class
  (flows into run_finished error_code + routine_runs.error_code for triage).
- web: resolveRunFailureUi maps AGENT_CONNECTION_DROPPED (any agent) to the
  localized chat.connectionDropped message, plain retry, no AMR promotion.
- i18n: add chat.connectionDropped to types.ts and all 19 locale bundles.

* fix(daemon): classify Claude in-stream connection-drop error frame

End-to-end repro on a real daemon + fake Claude CLI revealed the diagnostic
never fired: Claude surfaces a connection drop as an in-stream `error` frame
(assistant error:"unknown" + the raw SDK string), which claude-stream emits
as a type:'error' event. The daemon's stream-error handler sent that raw
string verbatim as a non-retryable AGENT_EXECUTION_FAILED and set
agentStreamError, so the child-exit diagnoseClaudeCliFailure path was skipped
entirely.

Run the same per-agent diagnostic in the stream-error handler so a connection
drop emits AGENT_CONNECTION_DROPPED (retryable, with localizable copy +
triage-countable code) instead of the raw SDK string. Verified end-to-end via
the real-daemon-run Playwright test, which now asserts the localized card copy.
2026-06-09 11:12:54 +00:00
..