Repository navigation
bin/vh tts: a 429 that names a per-day Gemini quota fails at once - #71
Merged
Merged
Conversation
Gemini's 429 for a spent daily quota can ask for a retryDelay under a minute, and gemini_call took a 429 for the daily quota only when it asked for more than 90 s. Below that it waited and retried up to 5 times a call, so --align gemini looked hung once the daily quota was spent. gemini_call now reads the quotas the 429 body names: a google.rpc.QuotaFailure violation whose quotaId or quotaMetric has "PerDay" in it fails the call at once with a message naming the quota and the --resume recovery. Per-minute 429s are waited out and retried as before; a 429 naming no daily quota still fails at once only when it asks for more than 90 s. tools/ci.sh checks both cases against a stub server on 127.0.0.1. The quota sentences in tools/audio/README and playbook/04 say the daily quota now fails at once.
The ci.sh stub check sets no_proxy for 127.0.0.1, so an exported http_proxy cannot turn it red, and covers the fallback too: a 429 naming no daily quota that asks for 120 s fails at once. tools/audio/README says again that a 429 with no delay counts as 60 s; the #70 changelog entry points at the new one.
ZLHad
marked this pull request as ready for review
October 7, 2026 15:17
ZLHad
enabled auto-merge (squash)
October 7, 2026 15:17
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.
Why
gemini_callintools/audio/tts.pydecided "daily quota" only from the delay a 429 asks for: over 90 s (RATE_WAIT_MAX) failed at once, anything shorter (or no delay, taken as 60 s) was waited out and retried up to 5 times a call (RATE_TRIES). Gemini's 429 for a spent per-day quota can ask for a shortretryDelay, so once the daily quota was spent,bin/vh tts … --align geminikept waiting and retrying and looked hung (a 2026-10 film; documented as a known behaviour in tools/audio/README "转写的配额" and playbook/04 "转写用不了时" by #70).What the real 429s look like (checked, not guessed):
google/rpc/error_details.proto:QuotaFailure { repeated Violation violations }, withViolationfieldssubject,description,api_service,quota_metric,quota_id,quota_dimensions,quota_value,future_quota_value; Gemini's REST bodies spell themquotaMetric,quotaId, … and add agoogle.rpc.RetryInfowithretryDelay.quotaId: "GenerateRequestsPerDayPerProjectPerModel-FreeTier"withretryDelay: "27s"; another listed…PerMinute…and…PerDay…violations together withretryDelay: "11.767792953s"(Generate error: 429 RESOURCE_EXHAUSTED. google-gemini/gemini-cli#8437 has the same mix with"55s"). In these thequotaMetricis e.g.generativelanguage.googleapis.com/generate_content_free_tier_requests, so in practice thequotaIdis what names the day.What changed
tools/audio/tts.py: newdaily_quota(body)returns thequotaId/quotaMetricof a QuotaFailure violation withPerDayin it, read from the first 8 KB of the body thatgemini_callalready reads. A 429 that names one raisesGeminiErrorat once, whatever delay it asks for:gemini interactions: the daily quota is spent (GenerateRequestsPerDayPerProjectPerModel); retrying will not help until it resets (bin/vh tts: then --resume continues the run). Per-minute 429s are unchanged (waited out, up to 5 retries), and a 429 naming no daily quota still fails at once only above 90 s.bin/vh voicesgoes through the same call.tools/ci.sh(smoke): a stub server on 127.0.0.1,GEMINI_APIpointed at it,time.sleeprecorded rather than slept: a 429 listing the per-minute and per-day quotas withretryDelay30s must raise after one request and no wait, naming the quota and--resume; two per-minute 429s and then a 200 must return the 200 after two 31 s waits; a 429 naming no daily quota that asks for 120 s must fail at once (the old fallback).no_proxyis set for 127.0.0.1 so an exportedhttp_proxycannot turn it red. It fails on the old tts.py ("a per-day 429 was retried (waits [31.0])"). Standard library only, under a second.CHANGELOG.mdUnreleased.Before / after
Reproduced with a local stub that answers every call with a per-day 429 (
retryDelay30s, body shaped like the real ones above), on a one-linesayscript with--align gemini, running tts.py'smain()in-process withGEMINI_APIpointed at the stub and the sleeps recorded:asr.error= the raw 429 JSON),_run.jsonasr.errornamesGenerateRequestsPerDayPerProjectPerModelAfter, the run ends with:
The same stub with only the per-minute quota in the body gives 5 waits of 31.4 s and then the failure both before and after: per-minute handling is unchanged.
Before this fix, with 4 lines transcribed at a time, each call spent about 2.5 minutes in waits before failing, so a long script sat in waits for many minutes after the daily quota ran out.
How it was verified
tools/ci.sh --committedwithVH_BASH=/bin/bash(bash 3.2) and with the default bash 5, shellcheck and pyflakes through the uv shim from CONTRIBUTING: 104 checks pass, none skipped.git grepfinds noAIzaSy…orAQ.…key.http_proxy, the over-90 s fallback case added to the check, the "no delay counts as 60 s" detail back in tools/audio/README, and a pointer from the Lessons from a narrated film with 3D: one voice in a one-request take, labels at the floor from the start, text swaps and readouts, glass in Three.js #70 changelog entry to this one. Kept on purpose: the--resumehint in theGeminiErrormessage (asked for;bin/vh voicessees it prefixedbin/vh tts:), andquotaMetricin the match (real metrics are snake_case, soquotaIdis what matches in practice).PerDayinquotaId. One that does not still falls back to the old 90 s rule.