headroomlabs-ai/headroom · warning · HTTPException
Rate limited. Retry after {wait_seconds:.1f}s
Error message
Rate limited. Retry after {wait_seconds:.1f}s What it means
The Gemini handler applies its own rate limit keyed by the inbound x-goog-api-key header (first 20 chars) before converting the request. On exhaustion it records a rate_limited metric and raises HTTPException 429 with detail 'Rate limited. Retry after Ns'. Unlike the Anthropic handler it does not set a Retry-After header and does not hold a pre-upstream semaphore at this point, so the parse must come from the detail message.
Source
Thrown at headroom/proxy/handlers/gemini.py:398
from headroom.proxy.helpers import get_memory_injection_mode, log_memory_injection
from headroom.proxy.memory_decision import MemoryDecision
from headroom.proxy.memory_query import MemoryQuery
memory_decision = MemoryDecision.decide(
headers=request.headers,
memory_handler=self.memory_handler,
memory_user_id=memory_user_id,
mode_name=get_memory_injection_mode(),
)
memory_decision.apply_to_tags(tags)
# Rate limiting (use Gemini API key)
if self.rate_limiter:
rate_key = headers.get("x-goog-api-key", "default")[:20]
allowed, wait_seconds = await self.rate_limiter.check_request(rate_key)
if not allowed:
await self.metrics.record_rate_limited(provider=provider_name)
raise HTTPException(
status_code=429,
detail=f"Rate limited. Retry after {wait_seconds:.1f}s",
)
# Convert Gemini format to messages for optimization
system_instruction = body.get("systemInstruction")
messages, preserved_indices = self._gemini_contents_to_messages(
contents, system_instruction
)
# Store original content entries that have non-text parts before compression
preserved_contents = {idx: contents[idx] for idx in preserved_indices}
# Early exit if ALL content has non-text parts (nothing to compress)
if len(preserved_indices) == len(contents):
# All content has non-text parts, skip compression entirely
# Just forward the request as-is
query_params = dict(request.query_params)View on GitHub (pinned to 322425c43b)
Solutions
- Parse the wait from the 429 detail text and back off at least that long before retrying.
- Raise the limiter's allowance for Gemini keys, or distribute clients across multiple keys so buckets don't collide.
- Add client-side request pacing/concurrency limits sized to the configured window.
Example fix
# before
for attempt in range(5):
resp = post_gemini(body) # immediate retries keep getting 429
# after
resp = post_gemini(body)
if resp.status_code == 429:
wait = float(re.search(r"after ([0-9.]+)s", resp.json()["detail"]).group(1))
time.sleep(wait + 0.5) Defensive patterns
Strategy: retry
Validate before calling
# No pre-check available; pace requests to the configured per-key window before sending.
Try / catch
resp = await client.post(gemini_url, json=body)
if resp.status_code == 429:
m = re.search(r"after ([0-9.]+)s", resp.json()["detail"])
await asyncio.sleep(float(m.group(1)) + 0.5 if m else 5.0)
resp = await client.post(gemini_url, json=body) Prevention
- Don't share one x-goog-api-key across many automated clients through the proxy.
- Implement exponential backoff that parses the wait from the 429 detail (no Retry-After header on this path).
- Keep aggregate request rate under the limiter's per-key window during batch jobs.
When it happens
Trigger: Sending more Gemini-format requests (x-goog-api-key auth) within the window than the limiter allows for that key prefix; multiple clients sharing one Google API key through the proxy.
Common situations: Several agents or team members configured with the same Gemini key funneled through one proxy; a client retry loop that ignores 429; per-key limit set lower than the aggregate fan-out.
Related errors
- Rate limited. Retry after {wait_seconds:.1f}s
- Budget exceeded for {budget_period} period
- failed to download {final_url} after {attempts} attempts: {e
- Error: Proxy dependencies not installed. Run: pip install he
- Shutting down...
AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15).
Data as JSON: /api/errors/acec5e18f2fc912a.
Report an issue: GitHub.