Files
openchamber/packages/ui/src/sync/retry.test.ts
T
herjarsa 39bb71a62b fix(ui): bound OpenCode read requests so half-open sockets cannot freeze bootstrap (#2470)
The SDK client fetch wrapper now applies a 30s timeout to non-streaming
reads. Without it, a socket that neither resolves nor rejects keeps the
directory bootstrap concurrency slot busy forever and the UI stays on
"loading sessions". Long-lived streams (POST prompts, the /event SSE)
are explicitly excluded so they are not cut off mid-flight.

The normalized "request timed out" error is added to the retry
allowlist alongside undici's "terminated" (the exact failure observed
in #2470 when undici tears down a half-open upstream connection); both
are transient while the managed OpenCode process restarts. Caller-
initiated aborts keep their original error shape so a user-cancelled
request is not retried.

Tests cover: GET timeout fires after the bound, POST is not timed out,
/event SSE is not timed out, caller abort wins, AbortError is not
retried, and the SDK normalized error is retried 3x.
2026-08-17 11:37:20 +02:00

39 lines
1.1 KiB
TypeScript

import { describe, expect, test } from 'bun:test';
import { retry } from './retry';
describe('retry transient classification (#2470)', () => {
test("'terminated' (undici half-open socket teardown) is retried", async () => {
let attempts = 0;
await expect(
retry(async () => {
attempts += 1;
throw new TypeError('terminated');
}),
).rejects.toThrow('terminated');
expect(attempts).toBe(3);
});
test("normalized 'request timed out' (SDK read timeout) is retried", async () => {
let attempts = 0;
await expect(
retry(async () => {
attempts += 1;
throw new Error('OpenCode request timed out after 30000ms');
}),
).rejects.toThrow('request timed out');
expect(attempts).toBe(3);
});
test('caller-initiated abort (AbortError) is NOT retried', async () => {
let attempts = 0;
await expect(
retry(async () => {
attempts += 1;
throw new DOMException('Aborted', 'AbortError');
}),
).rejects.toThrow('Aborted');
expect(attempts).toBe(1);
});
});