bug/proposal: infinitely hanging clients breaking bigger/complex sessions. Proposal: add Streaming Idle Timeout to Prevent Indefinite Hangs #867#868
Open
notactuallytreyanastasio wants to merge 3 commits intoanthropics:mainfrom
Conversation
Adds a new `idleTimeout` option to both `ClientOptions` and `RequestOptions`
that detects and aborts streams that stop sending SSE events.
When a stream stalls (server stops sending data without closing the connection),
the SDK will now throw a `StreamIdleTimeoutError` after the configured timeout,
allowing applications to handle the failure gracefully instead of hanging
indefinitely.
Changes:
- Add `idleTimeout` option to `ClientOptions` (default for all requests)
- Add `idleTimeout` option to `RequestOptions` (per-request override)
- Add `StreamIdleTimeoutError` class with diagnostic information
- Implement idle timeout in `_iterSSEMessages` using Promise.race
- Pass options through `Stream.fromSSEResponse` and response parsing
- Add comprehensive unit tests for idle timeout behavior
Usage:
```typescript
// Set default for all streams
const client = new Anthropic({ idleTimeout: 90_000 });
// Override per-request
const stream = await client.messages.stream(params, {
idleTimeout: 120_000,
});
```
- Timer cleanup on normal completion - Timer cleanup on user break/return - Timer cleanup on manual abort - Timer cleanup on processing error - Very short timeout behavior - Multiple sequential streams isolation - Accurate event count tracking - Memory leak prevention verification
- Test idleTimeout via request options - Test idleTimeout via client default - Test request options override client default - Test normal completion with timeout configured Verifies the full integration path from client.messages.stream() through to StreamIdleTimeoutError.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is an example PR for what's brought up in #867
I tried to ensure good test coverage.
Summary
This PR adds an
idleTimeoutoption to detect and abort streams that stop sending SSE events without closing the connection. This addresses a critical failure mode where streaming responses hang indefinitely when:The Problem
Currently, the SDK provides:
timeout: Overall request timeout (covers connection + response time)AbortController: Manual cancellation via signalsNeither detects stalled streams where the connection is alive but no data is flowing. When this happens,
for await (const event of stream)blocks forever with no error and no indication anything is wrong.Real-World Evidence
From production Claude Code CLI sessions, we observed:
"stop_reason": nullin logs - response never completedThe Solution
New
idleTimeoutoption that:Promise.raceto race each chunk read against a timeoutStreamIdleTimeoutErrorwith diagnostic information if the stream stallsChanges
src/client.tsidleTimeouttoClientOptionssrc/internal/request-options.tsidleTimeouttoRequestOptionssrc/core/error.tsStreamIdleTimeoutErrorclasssrc/core/streaming.ts_iterSSEMessagessrc/internal/parse.tsStream.fromSSEResponsesrc/index.tsStreamIdleTimeoutErrortests/streaming.test.tstests/api-resources/MessageStream.test.tsAPI
Type Safety
anytypes usedStreamIdleTimeoutErrorextendsAPIConnectionError(existing error hierarchy)idleTimeout?: number | undefinedto satisfyexactOptionalPropertyTypesTest Plan
npm run lintpassesnpm testpasses (427 tests total, including 17 new idle timeout tests)Test Coverage
Low-Level Unit Tests (
streaming.test.ts) - 13 testsidleTimeoutMs,eventCount,lastEventTimecontroller.abort()Client Integration Tests (
MessageStream.test.ts) - 4 testsclient.messages.stream(params, { idleTimeout })new Anthropic({ idleTimeout })Questions for Maintainers
idleTimeouthave a default (e.g., 2 minutes) or remain opt-in?pingSSE events reset the idle timer, or only "meaningful" events?maxRetries > 0?