Skip to content

fix(client): honor Retry-After delays up to two minutes - #3555

Merged
tescherm-openai merged 4 commits into
mainfrom
mattt/honor-long-retry-after
Jul 30, 2026
Merged

fix(client): honor Retry-After delays up to two minutes#3555
tescherm-openai merged 4 commits into
mainfrom
mattt/honor-long-retry-after

Conversation

@tescherm-openai

@tescherm-openai tescherm-openai commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

  • honor positive, finite Retry-After delays up to 120 seconds
  • treat longer server-directed delays as non-retryable instead of substituting short exponential backoff
  • keep exponential backoff for zero, negative, non-finite, missing, or malformed values
  • cover sync and async clients with numeric seconds, milliseconds, and HTTP-date boundaries

Motivation

The Python SDK currently ignores Retry-After values above 60 seconds and falls back to a much shorter exponential delay. Historical reset-header data shows that common minute-scale waits can land just above 60 seconds, while extending the ceiling beyond two minutes adds little coverage before waits jump to hours or days.

Developer impact

Clients with automatic retries enabled will honor server-directed delays through two minutes. If Retry-After exceeds two minutes, the SDK surfaces the API error instead of blocking a synchronous worker for an extended period or retrying before the requested time. Retry attempts remain bounded by max_retries.

Validation

  • .venv/bin/ruff format --check src/openai/_constants.py src/openai/_base_client.py tests/test_client.py
  • .venv/bin/ruff check src/openai/_constants.py src/openai/_base_client.py tests/test_client.py
  • .venv/bin/pytest -q tests/test_client.py (198 passed, 2 skipped)

@tescherm-openai
tescherm-openai marked this pull request as ready for review July 30, 2026 18:44
@tescherm-openai
tescherm-openai requested a review from a team as a code owner July 30, 2026 18:44
@openai-sdks

openai-sdks Bot commented Jul 30, 2026

Copy link
Copy Markdown

OkTest Summary

237/237 SDK tests passed in 11.956s for Python SDK PR #3555.

Test results — 42 files
Test Result Time
tests/chat-completions-complex-body.test.ts ✅ Passed 202ms
tests/chat-completions-create.test.ts ✅ Passed 272ms
tests/chat-completions-stream.test.ts ✅ Passed 155ms
tests/files-content-binary.test.ts ✅ Passed 248ms
tests/files-create-multipart.test.ts ✅ Passed 241ms
tests/files-list-pagination.test.ts ✅ Passed 203ms
tests/initialize-config.test.ts ✅ Passed 198ms
tests/instance-isolation.test.ts ✅ Passed 234ms
tests/models-list.test.ts ✅ Passed 141ms
tests/responses-background-lifecycle.test.ts ✅ Passed 222ms
tests/responses-body-method-errors.test.ts ✅ Passed 386ms
tests/responses-cancel-timeout.test.ts ✅ Passed 231ms
tests/responses-cancel.test.ts ✅ Passed 350ms
tests/responses-compact-retries.test.ts ✅ Passed 336ms
tests/responses-compact.test.ts ✅ Passed 250ms
tests/responses-create-advanced-stream.test.ts ✅ Passed 169ms
tests/responses-create-advanced.test.ts ✅ Passed 306ms
tests/responses-create-disconnect.test.ts ✅ Passed 1.279s
tests/responses-create-errors.test.ts ✅ Passed 206ms
tests/responses-create-malformed-api-responses.test.ts ✅ Passed 278ms
tests/responses-create-retries.test.ts ✅ Passed 371ms
tests/responses-create-stream-failures.test.ts ✅ Passed 133ms
tests/responses-create-stream-timeout.test.ts ✅ Passed 245ms
tests/responses-create-stream-wire.test.ts ✅ Passed 3.837s
tests/responses-create-stream.test.ts ✅ Passed 352ms
tests/responses-create-terminal-states.test.ts ✅ Passed 268ms
tests/responses-create-timeout.test.ts ✅ Passed 215ms
tests/responses-create.test.ts ✅ Passed 272ms
tests/responses-delete.test.ts ✅ Passed 240ms
tests/responses-input-items-errors.test.ts ✅ Passed 345ms
tests/responses-input-items-list.test.ts ✅ Passed 346ms
tests/responses-input-items-options.test.ts ✅ Passed 395ms
tests/responses-input-tokens-count-timeout.test.ts ✅ Passed 269ms
tests/responses-input-tokens-count.test.ts ✅ Passed 305ms
tests/responses-malformed-inputs.test.ts ✅ Passed 2.991s
tests/responses-not-found-errors.test.ts ✅ Passed 304ms
tests/responses-parse.test.ts ✅ Passed 413ms
tests/responses-retrieve-retries.test.ts ✅ Passed 298ms
tests/responses-retrieve.test.ts ✅ Passed 276ms
tests/responses-stored-method-errors.test.ts ✅ Passed 726ms
tests/retry-behavior.test.ts ✅ Passed 3.102s
tests/sdk-error-shape.test.ts ✅ Passed 357ms

View OkTest run #30584584865

SDK merge (f5f40e6ac3c9) · head (05b8ddf8b125) · base (3844843c277f) · OkTest (91635c6a2723)

@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: 6e55b90fed

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return timeout if timeout >= 0 else 0

def _should_retry(self, response: httpx.Response) -> bool:
retry_after = self._parse_retry_after_header(response.headers)

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 Ignore unconvertible Retry-After dates before retry checks

When automatic retries are enabled, a non-retryable error such as HTTP 400 can now raise ValueError or OverflowError instead of APIStatusError if it carries a syntactically parseable but out-of-range date, such as Retry-After: Fri, 29 Sep 100000 16:26:57 GMT: parsedate_tz() accepts it, but mktime_tz() in _parse_retry_after_header() fails. Previously _should_retry() did not parse this header for statuses that would not be retried, so conversion failures should be treated as malformed header values rather than escaping from this new call.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch. parsedate_tz() can accept dates that mktime_tz() cannot represent, so I updated _parse_retry_after_header() to treat TypeError, ValueError, OverflowError, and OSError as malformed header values. I also added sync and async regressions confirming that an HTTP 400 with this date still raises APIStatusError after one request, plus timeout-calculation coverage for the fallback behavior. The fix is committed locally as 8d149be8 and has not been pushed yet.

@HAYDEN-OAI HAYDEN-OAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the current head and found no substantive correctness or compatibility issues. The sync and async retry paths consistently honor finite server-directed delays through 24 hours, reject longer delays without retrying early, and preserve fallback/status-error behavior for malformed or non-finite values.

@tescherm-openai tescherm-openai changed the title fix(client): honor long Retry-After delays fix(client): honor Retry-After delays up to two minutes Jul 30, 2026
@tescherm-openai
tescherm-openai merged commit 7fa7946 into main Jul 30, 2026
20 checks passed
@tescherm-openai
tescherm-openai deleted the mattt/honor-long-retry-after branch July 30, 2026 22:14
@stainless-app stainless-app Bot mentioned this pull request Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants