Problem
The current retry logic in src/model.ts has several issues that cause hard failures on transient errors:
1. Thundering herd (no jitter)
The backoff uses pure exponential delay (BASE_DELAY_MS * 2^attempt), meaning all clients hitting the same error retry at identical intervals. This creates synchronized retry storms that amplify server load.
2. No error classification
All errors are treated equally — the code retries on auth failures (unauthorized), quota exceeded (insufficient_credit), and validation errors that will never succeed. This wastes time and delays the inevitable failure.
3. Duplicate content on mid-stream reconnect
streamWithReconnect() can reconnect even after text, reasoning, or tool-calls have already been emitted downstream. Reconnecting regenerates from scratch, producing duplicate content that corrupts the conversation.
4. Silent SSE error handling
In src/stream.ts, SSE error events are enqueued as {type: "error"} stream parts. The AI SDK treats this as regular content and never triggers its own error handling or retry logic. The stream completes "successfully" with an error buried in the content.
5. No HTTP status code retry
HTTP 429 (rate limit) and 5xx (server error) responses are not retried — they throw immediately instead of backing off.
Proposed Solution
Two PRs implement these improvements:
Together they create a clean error propagation chain:
SSE error → controller.error() (#11)
→ streamWithReconnect catches (#10)
→ shouldRetry() checks retryable + emittedContent (#10)
→ reconnect or fail with clear error
Test Coverage
88 tests passing, 0 failures:
- 22 model tests (9 new for retry logic)
- 23 stream tests (6 new for error handling)
- 43 existing tests (unchanged, still passing)
Environment Note
The auth/env tests (index.test.ts, auth.test.ts) are fragile when ~/.commandcode/auth.json exists in the test environment. PR #11 includes a fix that mocks the filesystem in those tests to isolate them from the real environment.
Problem
The current retry logic in
src/model.tshas several issues that cause hard failures on transient errors:1. Thundering herd (no jitter)
The backoff uses pure exponential delay (
BASE_DELAY_MS * 2^attempt), meaning all clients hitting the same error retry at identical intervals. This creates synchronized retry storms that amplify server load.2. No error classification
All errors are treated equally — the code retries on auth failures (
unauthorized), quota exceeded (insufficient_credit), and validation errors that will never succeed. This wastes time and delays the inevitable failure.3. Duplicate content on mid-stream reconnect
streamWithReconnect()can reconnect even after text, reasoning, or tool-calls have already been emitted downstream. Reconnecting regenerates from scratch, producing duplicate content that corrupts the conversation.4. Silent SSE error handling
In
src/stream.ts, SSEerrorevents are enqueued as{type: "error"}stream parts. The AI SDK treats this as regular content and never triggers its own error handling or retry logic. The stream completes "successfully" with an error buried in the content.5. No HTTP status code retry
HTTP 429 (rate limit) and 5xx (server error) responses are not retried — they throw immediately instead of backing off.
Proposed Solution
Two PRs implement these improvements:
PR feat: production-grade retry/backoff with error classification and partial output protection #10 — Retry/backoff core (
src/model.ts):[1s, 2.5s, 5s]with ±25% jitteremittedContenttracking — blocks reconnect if content already sentfetchOnce()for reconnects (no double retry counting)isRetryableStatus()for 429/5xxPR fix: propagate SSE errors as stream terminations instead of silent parts #11 — SSE error handling (
src/stream.ts, stacked on feat: production-grade retry/backoff with error classification and partial output protection #10):wrapError()normalizes SSE error objects to realErrorinstanceserrorevents →controller.error()(real termination)controller.error()(real termination)Together they create a clean error propagation chain:
Test Coverage
88 tests passing, 0 failures:
Environment Note
The auth/env tests (
index.test.ts,auth.test.ts) are fragile when~/.commandcode/auth.jsonexists in the test environment. PR #11 includes a fix that mocks the filesystem in those tests to isolate them from the real environment.