Skip to content

fix(agent): trigger fallback on quota-exhaustion 429s (e.g. Kimi monthly limit) - #36280

Open
02356abc wants to merge 1 commit into
NousResearch:mainfrom
02356abc:fix/429-quota-exhaustion-billing
Open

fix(agent): trigger fallback on quota-exhaustion 429s (e.g. Kimi monthly limit)#36280
02356abc wants to merge 1 commit into
NousResearch:mainfrom
02356abc:fix/429-quota-exhaustion-billing

Conversation

@02356abc

@02356abc 02356abc commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Problem

HTTP 429 responses carrying a permanent quota-exhaustion message (e.g. Kimi's "monthly usage limit") were not triggering the fallback_model chain. Two separate gaps caused this:

  1. error_classifier.py: 429 was unconditionally classified as rate_limit (retryable), burning 3 retry attempts with backoff before giving up — even though a monthly billing-cycle limit won't recover within any retry window.

  2. agent_runtime_helpers.py: recover_with_credential_pool used hardcoded strings that didn't cover Kimi's "monthly usage limit" phrasing, so fallback_model was never triggered.

Fixes #36276.

Fix

Part 1 — error_classifier.py: Apply _BILLING_PATTERNS + _USAGE_LIMIT_PATTERNS / transient-signal disambiguation to the 429 branch (same as 402/400/no-status paths). Plain 429s with no quota phrase remain rate_limit retryable — no regression.

Part 2 — agent_runtime_helpers.py: Replace two ad-hoc hardcoded strings with the shared _USAGE_LIMIT_PATTERNS list so fallback trigger stays in sync with the classifier.

Test plan

  • All 184 agent tests pass (tests/agent/)
  • Kimi "monthly usage limit" 429 now classifies as billing and triggers fallback_model
  • Normal rate-limit 429s remain retryable — no regression

🤖 Generated with Claude Code

@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint provider/kimi Kimi / Moonshot P2 Medium — degraded but workaround exists labels Jun 1, 2026
…hly limit)

Two-part fix for the same bug:

1. error_classifier.py: apply _BILLING_PATTERNS + _USAGE_LIMIT_PATTERNS
   disambiguation to the 429 branch, same as the existing 402/400/no-status
   paths. Kimi's "monthly usage limit" 429 now classifies as billing instead
   of rate_limit, avoiding 3 wasted retry attempts with backoff.

2. agent_runtime_helpers.py: replace the ad-hoc hardcoded strings in
   recover_with_credential_pool with the shared _USAGE_LIMIT_PATTERNS list
   so the fallback_model chain is actually triggered when quota is exhausted.
   Previously "monthly usage limit" was not matched and the error surfaced
   directly to the user instead of falling back.

A plain 429 with no billing/quota phrase continues to classify as rate_limit
(retryable=True) — no regression for normal throttling.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@02356abc
02356abc force-pushed the fix/429-quota-exhaustion-billing branch from 132f424 to 6d9b1b4 Compare June 1, 2026 10:50
@02356abc 02356abc changed the title fix(agent): disambiguate HTTP 429 quota-exhaustion as billing, not rate_limit fix(agent): trigger fallback on quota-exhaustion 429s (e.g. Kimi monthly limit) Jun 1, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for isolating a real classifier gap. Current main still defaults this permanent-quota 429 shape to rate_limit at agent/error_classifier.py:954-989, so the core fix remains useful.

Problems

  • The tests added in 6d9b1b48 call only classify_api_error. They do not cover the live recovery/fallback path, which currently passes the structured reason at agent/conversation_loop.py:2644-2649 and activates fallback at agent/conversation_loop.py:3193-3229.
  • The agent_runtime_helpers.py hunk is stale on current main: the structured classified_reason already reaches recovery, and its billing path rotates immediately at agent/agent_runtime_helpers.py:796-807. The 429 branch also now has overload and OpenRouter-upstream cases at agent/error_classifier.py:963-983 that must remain ahead of the new permanent-quota rule.

Suggested changes

  • Salvage the classifier rule after those current special cases and add a runtime test proving a monthly-usage-limit 429 activates the configured fallback without retry/backoff.
  • Omit the obsolete raw-string helper hunk unless a current caller without classified_reason is found.

Automated hermes-sweeper review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists provider/kimi Kimi / Moonshot sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(agent): HTTP 429 quota-exhaustion errors retried 3 times instead of failing fast

3 participants