feat(sse): restate agentrouter quota 403/400 as retryable 429 with provider-scoped error rules - #10335
Merged
diegosouzapw merged 10 commits intoAug 14, 2026
Merged
Conversation
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.
…router-quota-restatement
…router-quota-restatement
This was referenced Aug 14, 2026
Merged
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.
Problem
The agentrouter.org gateway signals temporary quota exhaustion with HTTP
403(sometimes400) and a Chinese body (用户额度不足— "user quota insufficient") instead of the standard429. Two consequences:403as permanent and abort the whole session — the exact failure the external ClaudeShield proxy was built to work around.AUTH_ERROR, and the raw403propagated to the client without aRetry-After(403 is not Retry-After-eligible incombo/unavailableRetryGate.ts).Solution (no external proxy needed)
open-sse/config/upstreamStatusRestatement.ts: per-provider rules{fromStatuses, textMarkers, excludeMarkers, toStatus, defaultRetryAfterMs}. For agentrouter:403/400+额度不足→429with a synthetic 60sRetry-After(upstream retry hints take precedence).无权访问模型(genuinely permanent, no model access) is veto-listed and never restated. Applied at a single hook inchatCore.ts'sproviderFailure:block, right afterparseUpstreamError(), so fallback classification, combo aggregation and the surfaced client status all see the corrected429.open-sse/config/providerErrorRules.ts(quota →quota_exhausted; model-access-denied → declarativeauth_error). Activated on the realcheckFallbackErrorpath via the newresolveRuleMatchBody()helper with an exclusive allowlistFULL_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).docs/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'
scopefield is informational — persistence still applies per-model lockout for agentrouter (passthroughModels); the 403-only model-access rule has no production path yet; errors embedded inside200SSE streams are not restated (separate stream-parsing path).Validation
tests/unit/upstream-status-restatement.test.ts(10) andtests/unit/agentrouter-error-rules.test.ts(10), plus strengthened sibling coverage; every rule branch red-then-green.provider-error-rules+openrouter-quota-6842+cloudflare-ai-neuron-exhaustion-6980).typecheck:corePASS,check:cyclesPASS (412 files),check:docs-allPASS,check:file-sizewithin frozen cap.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 cleanorigin/release/v3.8.50worktree; 5 structurally unreachable by this diff).lint/typecheck:noimplicit:coreerrors are pre-existing on the base tip (byte-identical files, no import relationship to this diff).Refs #10334