Skip to content

Commit c40ef3f

Browse files
fix(core): narrow timeout detection to gateway failure shapes
1 parent 603f091 commit c40ef3f

2 files changed

Lines changed: 21 additions & 6 deletions

File tree

‎packages/core/src/session/runner/retry.ts‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,15 @@ const MAX_TIMEOUT_RETRIES = 3
6161

6262
// Gateways report their own provider timeouts as ordinary failures: OpenCode Zen answers a dead
6363
// upstream in-band with `Streaming response failed: [504] Provider timed out after Nms`, and the
64-
// status never reaches the reason. The status and message checks keep those attempts on the same
65-
// cap as local transport timeouts instead of the full general allowance.
64+
// status never reaches the reason. Restricting the status and message checks to the gateway
65+
// failure shapes keeps WebSocket idle/connect timeouts and unrelated messages on the general
66+
// allowance.
6667
const isTimeout = (error: AIError) =>
67-
error.reason._tag === "Timeout" ||
6868
(error.reason._tag === "Transport" && error.reason.code === "Timeout") ||
69-
error.reason.http?.status === 408 ||
70-
error.reason.http?.status === 504 ||
71-
/\btimed?[- ]?out\b/i.test(error.reason.message)
69+
((error.reason._tag === "ProviderInternal" || error.reason._tag === "UnknownProvider") &&
70+
(error.reason.http?.status === 408 ||
71+
error.reason.http?.status === 504 ||
72+
/\btimed?[- ]?out\b/i.test(error.reason.message)))
7273

7374
export const policy = (sessionID: SessionSchema.ID) =>
7475
Effect.gen(function* () {

‎packages/core/test/session-runner-retry.test.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ const gatewayTimeout = new AIError({
1919
const badGateway = new AIError({
2020
reason: new ProviderInternalError({ message: "Streaming response failed: [502] Upstream error" }),
2121
})
22+
const socketTimeout = new AIError({
23+
reason: new TransportError({
24+
message: "Timed out waiting for WebSocket data",
25+
transport: "websocket",
26+
operation: "read",
27+
code: "idle-timeout",
28+
}),
29+
})
2230

2331
const input = (cause: AIError) => ({
2432
cause,
@@ -74,4 +82,10 @@ describe("SessionRunnerRetry.policy", () => {
7482
expect(yield* decisions(badGateway, 10)).toEqual(Array(10).fill(true))
7583
}),
7684
)
85+
86+
it.effect("keeps websocket idle timeouts on the general allowance", () =>
87+
Effect.gen(function* () {
88+
expect(yield* decisions(socketTimeout, 10)).toEqual(Array(10).fill(true))
89+
}),
90+
)
7791
})

0 commit comments

Comments
 (0)