Skip to content

feat(sse): restate agentrouter quota 403/400 as retryable 429 with provider-scoped error rules - #10335

Merged
diegosouzapw merged 10 commits into
release/v3.8.50from
feat/agentrouter-quota-restatement
Aug 14, 2026
Merged

feat(sse): restate agentrouter quota 403/400 as retryable 429 with provider-scoped error rules#10335
diegosouzapw merged 10 commits into
release/v3.8.50from
feat/agentrouter-quota-restatement

Conversation

@diegosouzapw

Copy link
Copy Markdown
Owner

Problem

The agentrouter.org gateway signals temporary quota exhaustion with HTTP 403 (sometimes 400) and a Chinese body (用户额度不足 — "user quota insufficient") instead of the standard 429. Two consequences:

  • Client side: Claude Code (and most clients) treat 403 as permanent and abort the whole session — the exact failure the external ClaudeShield proxy was built to work around.
  • Router side: OmniRoute classified it as a generic apikey AUTH_ERROR, and the raw 403 propagated to the client without a Retry-After (403 is not Retry-After-eligible in combo/unavailableRetryGate.ts).

Solution (no external proxy needed)

  1. Upstream status restatement — new registry open-sse/config/upstreamStatusRestatement.ts: per-provider rules {fromStatuses, textMarkers, excludeMarkers, toStatus, defaultRetryAfterMs}. For agentrouter: 403/400 + 额度不足429 with a synthetic 60s Retry-After (upstream retry hints take precedence). 无权访问模型 (genuinely permanent, no model access) is veto-listed and never restated. Applied at a single hook in chatCore.ts's providerFailure: block, right after parseUpstreamError(), so fallback classification, combo aggregation and the surfaced client status all see the corrected 429.
  2. Provider-scoped classification rules — agentrouter registered in open-sse/config/providerErrorRules.ts (quota → quota_exhausted; model-access-denied → declarative auth_error). Activated on the real checkFallbackError path via the new resolveRuleMatchBody() helper with an exclusive allowlist FULL_TEXT_RULE_PROVIDERS = {"agentrouter"}: only allowlisted providers get the full error text at the rule matcher — every other provider's path is byte-for-byte unchanged (proven by test A10 against a pre-change captured baseline).
  3. Docsdocs/architecture/RESILIENCE_GUIDE.md §7 documents the mechanism and the recipe for registering future quota-misstating gateways (registry entries + tests, no pipeline changes).

Known limitations (deliberate, tracked in #10334): the rules' scope field is informational — persistence still applies per-model lockout for agentrouter (passthroughModels); the 403-only model-access rule has no production path yet; errors embedded inside 200 SSE streams are not restated (separate stream-parsing path).

Validation

  • TDD throughout: 25 new tests across tests/unit/upstream-status-restatement.test.ts (10) and tests/unit/agentrouter-error-rules.test.ts (10), plus strengthened sibling coverage; every rule branch red-then-green.
  • Focused suites: 59/59 PASS (new + provider-error-rules + openrouter-quota-6842 + cloudflare-ai-neuron-exhaustion-6980).
  • Gates: typecheck:core PASS, check:cycles PASS (412 files), check:docs-all PASS, check:file-size within frozen cap.
  • Full test:unit: 30,959 pass across 3 phases; all 16 phase-1 failures discriminated — 4 load-contention flakes (PASS re-run isolated), 12 deterministic pre-existing base reds (7 reproduced byte-identical on a clean origin/release/v3.8.50 worktree; 5 structurally unreachable by this diff). lint/typecheck:noimplicit:core errors are pre-existing on the base tip (byte-identical files, no import relationship to this diff).

⚠️ base-red inherited: #9985

Refs #10334

Corrects text-only overclaims found in final review: ProviderErrorRuleMatch.scope
is not consumed by checkFallbackError/combo.ts (agentrouter quota_exhausted
resolves to per-model lockout, not a connection-wide lock), and
agentrouter-model-access-denied never reaches production traffic (the apikey
FORBIDDEN branch returns early for a plain 403 before provider rules run).
Also documents the restatement marker-echo trade-off and clarifies the
synthetic Retry-After vs internal cooldown distinction. No behavior changes.
@diegosouzapw
diegosouzapw merged commit 20ea78c into release/v3.8.50 Aug 14, 2026
11 checks passed
@diegosouzapw
diegosouzapw deleted the feat/agentrouter-quota-restatement branch August 14, 2026 15:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants