Repository navigation
fix(core): honor a RetryInfo delay of zero when classifying quota errors - #29532
Linxiushen wants to merge 1 commit into
Conversation
|
📊 PR Size: size/M
|
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses a bug where a server-provided retry delay of zero was being discarded due to an incorrect truthiness check. This caused routine rate-limit errors to be misclassified as terminal errors, prematurely aborting the retry flow. The changes ensure that zero-second delays are honored, allowing the system to properly attempt retries instead of falling back to terminal error handling. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request updates the classifyGoogleError function in packages/core/src/utils/googleQuotaErrors.ts to correctly handle a zero retry delay (e.g., 0s or 0ms) instead of discarding it as a falsy value. It replaces the implicit falsy checks with explicit comparisons against null and undefined. Additionally, comprehensive unit tests have been added in googleQuotaErrors.test.ts to verify the classification of errors with zero delays, unparsable delays, and retryable quota errors. There are no review comments, and the changes look correct and well-tested.
a6a19ca to
dfd9d18
Compare
`parseDurationInSeconds()` documents `null` as its "cannot parse" sentinel and returns a number otherwise, so "0s" / "0ms" parse to `0`. But `classifyGoogleError()` guarded the result with a truthiness check (`if (parsedDelay)`), so a valid zero delay was discarded and `delaySeconds` stayed `undefined`. For Cloud Code `RATE_LIMIT_EXCEEDED` that lands in the `delaySeconds === undefined` branch, which returns a `TerminalQuotaError`: a server telling the client to retry immediately aborted retries and triggered the terminal quota/fallback flow instead. The same truthiness guard on the generic RetryInfo path (`retryInfo?.retryDelay && delaySeconds`) skipped the retryable branch for a zero delay. Compare against the documented sentinels instead (`parsedDelay !== null` and `delaySeconds !== undefined`). Unparsable durations still fall through to the existing behavior. Fixes google-gemini#29049
dfd9d18 to
e3b42f0
Compare
|
The Commit Re-verified locally on the amended commit:
|
chrikrah
left a comment
There was a problem hiding this comment.
@Linxiushen I would merge this. I reverted googleQuotaErrors.ts to 2fe7c2d at e3b42f0 and kept your tests. The three zero-delay cases then fail, and the other 49 still pass.
$ cd packages/core && npx vitest run src/utils/googleQuotaErrors.test.ts # at e3b42f0, node v22.23.3
Tests 52 passed (52)
# src/utils/googleQuotaErrors.ts reverted to 2fe7c2d, tests kept
Tests 3 failed | 49 passed (52)
# not run: src/utils/retry.test.ts and the cli package
The limit: 0 fail-fast from #27698 runs before the RetryInfo step, so a zero-quota response that also carries retryDelay: "0s" stays terminal.
@luisfelipe-alt, you wrote the last three changes to this classifier. #29049 is still status/need-triage, and gemini-lifecycle-manager.cjs closes a pull request without help wanted a week after its nudge. Can this one get the label?
|
Hi there! Thank you for your interest in contributing to Gemini CLI. To ensure we maintain high code quality and focus on our prioritized roadmap, we only guarantee review and consideration of pull requests for issues that are explicitly labeled as 'help wanted'. This PR will be closed in 7 days if it remains without that designation. We encourage you to find and contribute to existing 'help wanted' issues in our backlog! Thank you for your understanding. |
Summary
classifyGoogleError()discarded a server-suppliedRetryInfodelay of zero,so a routine rate limit that the server said to retry immediately was
misclassified as a terminal quota error. That aborted retries and fired the
terminal quota / model-fallback / credits flow instead of simply retrying. This
changes two truthiness checks into comparisons against the documented sentinel
values.
Details
Root cause
parseDurationInSeconds()inpackages/core/src/utils/googleQuotaErrors.tsdocuments
nullas its "cannot parse" sentinel and returns a number otherwise,so
"0s"and"0ms"parse to0. ButclassifyGoogleError()guarded theresult with a truthiness check:
A delay of zero — "retry immediately" — was therefore thrown away and
delaySecondsstayedundefined, indistinguishable from a malformed duration.Two call sites downstream then took the wrong branch:
RATE_LIMIT_EXCEEDEDexplicitly returns aTerminalQuotaErrorwhen
delaySeconds === undefined, so a routine rate limit became terminal.retryInfo?.retryDelay && delaySeconds, so a zero delay skipped theretryable branch and fell through to the generic tail, losing the
Suggested retry after ...message and the delay.The sibling
Please retry in Xfallback in the same function already comparesagainst the documented sentinel (
if (retryDelaySeconds !== null)), so this wasan internal inconsistency rather than intended behaviour.
Fix
Compare against the documented sentinels at both sites:
parsedDelay !== nullwhen reading
RetryInfo, anddelaySeconds !== undefinedat theRetryInfoclassification branch. Two lines plus a comment; no other behaviour changes.
Unparsable durations still leave
delaySecondsundefined and keep their currentbehaviour (Cloud Code
RATE_LIMIT_EXCEEDEDwith a garbage duration staysterminal), and the 5-minute terminal threshold is untouched.
Note on the resulting delay:
RetryableQuotaError's constructor maps a 0 sdelay to
retryDelayMs === undefined, soretryWithBackoffuses its normalexponential backoff rather than retrying with no pause. The behavioural change
is purely in the classification — terminal to retryable — which is the point; it
deliberately does not introduce a zero-delay hot retry loop.
Related Issues
Fixes #29049.
How to Validate
From the repository root:
What actually ran here (node v24.20.0, vitest 3.2.4, macOS):
googleQuotaErrors.test.ts— 52 tests, all pass.googleQuotaErrors.test.ts+retry.test.ts+googleErrors.test.ts—3 files, 118 tests, all pass.
prettier --check,eslint, andtsc --noEmit -p packages/coreare clean onthe changed files.
Four cases were added to
googleQuotaErrors.test.ts: Cloud CodeRATE_LIMIT_EXCEEDEDwithretryDelay: "0s"and with"0ms"(both nowRetryableQuotaError), a bareRetryInfowith"0s"(nowRetryableQuotaErrorcarryingSuggested retry after 0s.), and a guard caseasserting that an unparsable duration still yields a
TerminalQuotaErrorwithreason
RATE_LIMIT_EXCEEDED.To confirm the first three are genuine regression tests, revert only the source
file and re-run:
git stash push -- packages/core/src/utils/googleQuotaErrors.ts # keep the tests npx vitest run src/utils/googleQuotaErrors.test.ts --root packages/core git stash popThey fail on unmodified
mainwithexpected TerminalQuotaError: Rate limit exceeded to be an instance of RetryableQuotaErrorand
expected 'Too many requests' to be 'Too many requests\nSuggested retry after 0s.'.The fourth (unparsable duration) passes on both sides by design — it is a guard
against over-correction, not a regression test.
Not run: the full
npm run preflightand the integration test suites.Pre-Merge Checklist
user-facing surface change beyond the corrected error classification
Left unchecked deliberately: this is a pure classification-logic change inside
packages/core, exercised by the unit tests above on macOS. It was notvalidated by driving the CLI end-to-end on each platform/method, so I have not
claimed those boxes.
🤖 Generated with Claude Code