Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit bcef67e

Browse files
authored
fix(agent): classify mid-response stream deaths as transient upstream failures
Claude Code surfaces an SSE stream that dies after content started as "API Error: Connection closed mid-response. The response above may be incomplete." classifyAgentError did not recognize that string (nor the transport-level "socket connection was closed" variants), so these transient transport failures fell through to agent_error and hard-failed the whole task run with a raw error message instead of being treated as retriable upstream failures. Generated-By: PostHog Code Task-Id: a04c4d02-9070-4e5d-9748-bfceb251e3fa
1 parent 59f538b commit bcef67e

2 files changed

Lines changed: 39 additions & 0 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { describe, expect, it } from "vitest";
2+
import { classifyAgentError } from "./error-classification";
3+
4+
describe("classifyAgentError", () => {
5+
it.each([
6+
["API Error: terminated", "upstream_stream_terminated"],
7+
[
8+
"API Error: Connection closed mid-response. The response above may be incomplete.",
9+
"upstream_stream_terminated",
10+
],
11+
[
12+
"API Error: The socket connection was closed unexpectedly.",
13+
"upstream_stream_terminated",
14+
],
15+
["API Error: socket connection closed", "upstream_stream_terminated"],
16+
["API Error: Connection error.", "upstream_connection_error"],
17+
["API Error: Request timed out.", "upstream_timeout"],
18+
["API Error: 429 rate limited", "upstream_provider_failure"],
19+
["API Error: 529 overloaded", "upstream_provider_failure"],
20+
["API Error: 400 invalid request", "agent_error"],
21+
["Connection closed mid-response without the API Error prefix", "agent_error"],
22+
["some unrelated failure", "agent_error"],
23+
[undefined, "agent_error"],
24+
] as const)("classifies %j as %s", (message, expected) => {
25+
expect(classifyAgentError(message)).toBe(expected);
26+
});
27+
});

‎packages/agent/src/adapters/error-classification.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ export function classifyAgentError(
2323
if (/API Error:\s*terminated\b/i.test(text)) {
2424
return "upstream_stream_terminated";
2525
}
26+
// Claude Code surfaces an SSE stream that dies after content started
27+
// (no message_stop) as "Connection closed mid-response". Seen when a
28+
// gateway pod is replaced mid-stream or an intermediary cuts the socket
29+
// during a long silent stretch.
30+
if (/API Error:.*Connection closed mid-response/i.test(text)) {
31+
return "upstream_stream_terminated";
32+
}
33+
// Transport-level socket deaths reported by fetch implementations
34+
// (Bun/undici wording varies) — same failure mode as above.
35+
if (/API Error:.*socket connection (?:was )?closed/i.test(text)) {
36+
return "upstream_stream_terminated";
37+
}
2638
if (/API Error:\s*Connection error\b/i.test(text)) {
2739
return "upstream_connection_error";
2840
}

0 commit comments

Comments
 (0)