fix(openai): honor Gemini RetryInfo retryDelay on 429/503 retries - #271
Merged
Merged
Conversation
Gemini's OpenAI-compatible endpoint advertises its cooldown only as google.rpc.RetryInfo inside the error JSON (error.details[].retryDelay, a protobuf Duration like "39s") and sends no retry-after header. The retry loop derived retryAfterMs solely from the header, so throttled requests burned all three attempts on the 150/300ms backoff before the provider's cooldown had elapsed. httpErrorFromResponse now falls back to parsing that structured delay from the already-read body on 429/503 when the header is absent. The value flows through the existing retryAfterMs field, so header precedence, the 5s interactive cap in resolveWaitMs, and the cancellation-aware sleep all apply unchanged; malformed or negative durations are ignored. Fake-timer tests cover each acceptance criterion of #106. Fixes #106 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
plombeer31
pushed a commit
that referenced
this pull request
Aug 31, 2026
…6-08-31 # Conflicts: # src/agent/step-executor.ts # src/config/config-schema.ts
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.
What was broken
Gemini's OpenAI-compatible endpoint advertises its cooldown only as structured error JSON — a
google.rpc.RetryInfoentry inerror.details[]whoseretryDelayis a protobuf Duration string ("39s", "1.5s") — and sends noRetry-Afterheader.httpErrorFromResponseinsrc/llm/provider/openai/openai-http.tsderivedretryAfterMsonly from the header, soresolveWaitMsnever saw the provider cooldown and throttled requests exhausted all three attempts on the 150/300 ms exponential backoff before the advertised cooldown elapsed.The fix
When the
retry-afterheader is absent on a 429/503,httpErrorFromResponsenow falls back toparseRetryInfoDelayMs(), which JSON-parses the already-read body, walkserror.details[]for a stringretryDelaymatching^(\d+(?:\.\d+)?)s$, and returns milliseconds. The value flows through the existingretryAfterMsfield, so nothing else changes:Retry-Afterheader still takes precedence;resolveWaitMsstill caps the wait atOPENAI_RETRY_AFTER_CAP_MS(5 s), so a "39s" cooldown cannot stall an interactive turn or sleep unboundedly;sleep()still lets a caller abort interrupt the wait;Test evidence
New fake-timer tests (pinned
Math.randomzeroes the jitter; no wall-clock delay) insrc/llm/provider/openai/openai-http.test.tscover every acceptance criterion: headerless 429 with "1.5s" and 503 with "2s" control the retry timing,"not-a-duration"/"-1s"/ non-string values fall back to the 150 ms backoff, a valid header beats the structured delay, "39s" is capped at 5 s, a 500 carrying RetryInfo-shaped JSON ignores it, and caller abort interrupts the pending wait.npx vitest run src/llm/provider/openai— 95 passed (9 files)npm run lint(tsc --noEmit) — cleannpm run build— cleanNote: open PR #267 addresses the same issue with an equivalent approach; this PR was produced from an independent verification pass against v0.4.2
main(4caff55) and adds a regression test that the 429/503-only gate holds. Maintainers can merge either.Fixes #106