Skip to content

fix(client): apply timeout to response body reads - #1908

Closed
redactdeveloper wants to merge 1 commit into
openai:mainfrom
redactdeveloper:fix/1825-body-read-timeout
Closed

fix(client): apply timeout to response body reads#1908
redactdeveloper wants to merge 1 commit into
openai:mainfrom
redactdeveloper:fix/1825-body-read-timeout

Conversation

@redactdeveloper

Copy link
Copy Markdown

Summary

  • keep request timeouts active during stalled response body reads, not only until headers arrive
  • start the body timeout lazily when the response body is actually consumed, so asResponse() callers do not leave active timers behind
  • release the response body reader when the monitored stream closes, errors, or is cancelled
  • add regression coverage for a response that sends headers and then stalls before completing the body

Tests

  • ./scripts/test tests/index.test.ts
  • ./node_modules/.bin/jest tests/index.test.ts --runInBand --detectOpenHandles
  • ./node_modules/.bin/prettier --check src/client.ts tests/index.test.ts
  • ./node_modules/.bin/tsc --noEmit --skipLibCheck --target es2020 --lib es2020,dom,dom.iterable --module commonjs --moduleResolution node --esModuleInterop --strict --exactOptionalPropertyTypes --noUncheckedIndexedAccess --noImplicitOverride --noPropertyAccessFromIndexSignature src/client.ts

Fixes #1825

Previously the request timeout was cleared as soon as response headers arrived, leaving the body read (response.json(), streaming, etc.) unguarded. A server that sent headers and then stalled mid-body could hang the SDK indefinitely.

The abort timer now stays armed through the body read and is re-armed on each chunk, so a stalled body is aborted after the configured timeout of inactivity while a steadily-streaming response is never cut off. The timer is cleared once the body is fully read, cancelled, or errors.

Fixes openai#1825
@redactdeveloper
redactdeveloper requested a review from a team as a code owner June 1, 2026 06:17

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2714d71129

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/client.ts
Comment on lines +1020 to +1021
rearmTimeout();
const { done, value } = await reader.read();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Route body timeouts through retry handling

When the server sends headers and then stalls the body, the new timer now fires from this pull() after makeRequest() has already returned a successful APIResponseProps, so the existing timeout classification/retry path around fetchWithAuth() is bypassed. With the default maxRetries, a stalled JSON or SSE body will surface as the raw abort/read error and will not be retried or normalized as APIConnectionTimeoutError, even though the client documents request timeouts as retryable.

Useful? React with 👍 / 👎.

Comment thread src/client.ts
// leave an active timer behind.
clearCurrentTimeout();

const reader = response.body.getReader();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard custom fetch bodies before calling getReader

In Node 20+ globalThis.ReadableStream exists, so any custom fetch that returns a node-fetch-style response with a Node Readable body reaches this path, but those bodies do not implement getReader(). Previously such responses could still be parsed via their own Response.text()/json() methods; now the request throws TypeError: response.body.getReader is not a function before returning, so custom fetch implementations with non-WHATWG bodies need to be left unwrapped or checked for getReader first.

Useful? React with 👍 / 👎.

@HAYDEN-OAI

Copy link
Copy Markdown
Contributor

Thanks for investigating stalled response bodies! A complete fix needs coordinated SDK response parsing, timeout classification, and retry handling; the current implementation bypasses retries after headers arrive and also needs to preserve custom fetch compatibility. I’m moving this to SDK triage so the complete response lifecycle can be reviewed together.

@HAYDEN-OAI HAYDEN-OAI added sdk and removed upstream Needs to be addressed in Stainless upstream labels Aug 11, 2026
@HAYDEN-OAI

Copy link
Copy Markdown
Contributor

Thanks for investigating this; stalled response bodies are a real bug. I traced the current request lifecycle to the shared Castiron TypeScript client template, which clears its timeout as soon as fetch resolves and owns timeout classification and retries. Fixing this safely therefore requires coordinated upstream changes across generated request retries, response parsing, and streaming rather than an SDK-local response wrapper. The proposed wrapper also bypasses APIConnectionTimeoutError/retries after headers, eagerly consumes raw responses, breaks custom fetch bodies without getReader(), and can silently truncate SSE streams. I am marking this upstream so the complete fix can be made in Castiron and propagated safely.

@HAYDEN-OAI HAYDEN-OAI added upstream Needs to be addressed in Stainless upstream and removed sdk labels Aug 12, 2026
@HAYDEN-OAI

Copy link
Copy Markdown
Contributor

Thanks for investigating this. The canonical response-body timeout fix has now merged upstream in the shared TypeScript client generator, including timeout classification and retry handling while preserving raw responses, streaming, and custom fetch behavior. Because src/client.ts is generated, closing this SDK-local proposal as superseded. The fix will be available after SDK regeneration and release.

@HAYDEN-OAI HAYDEN-OAI closed this Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

upstream Needs to be addressed in Stainless upstream

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SDK timeout option does not cover response-body read phase (can hang forever)

2 participants