Skip to content

fix(auth): model-scoped lockout on passthrough provider credit exhaustion - #13548

Merged
diegosouzapw merged 11 commits into
cheaper-inference:release/v3.8.51from
ggdayup:fix/passthrough-credit-lockout
Sep 29, 2026
Merged

diegosouzapw merged 11 commits into
cheaper-inference:release/v3.8.51from
ggdayup:fix/passthrough-credit-lockout

Conversation

@ggdayup

@ggdayup ggdayup commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Summary

When a provider with passthrough models (e.g., bai / thebai, third-party aggregators, or multi-model nodes) returns HTTP 400 with credit insufficient balance or quota exhaustion on a specific paid model, OmniRoute previously marked the entire connection as terminal credits_exhausted with rateLimitedUntil = null.

This disabled all free or available models on the same connection (e.g., bai/qwen3.8-flash) until a manual "Retest" was triggered or the 30-minute background reprobe timer elapsed.

This PR ensures credit exhaustion on per-model-quota providers is properly scoped as a model-level lockout (reason: "quota_exhausted") rather than a whole-connection terminal failure.

Root Cause

  1. checkFallbackError() in open-sse/services/accountFallback.ts:
    PR fix(providers): cegp/gpt-5.6-luna dead — 403 insufficient_quota reset never fires, should mark model unhealthy #8247 added !isCompatibleProvider(provider) to prevent compatible providers from emitting creditsExhausted: true. However, isCompatibleProvider only matches openai-compatible- / anthropic-compatible- prefixes, omitting built-in passthrough providers like bai.
  2. markAccountUnavailable() in src/sse/services/auth.ts:
    The model-lockout conditional check was restricted to (status === 404 || isNvidiaModelGone || status === 429 || status >= 500). When an upstream passthrough provider returns HTTP 400 with credit insufficient balance, status 400 did not match this condition, falling through to resolveTerminalConnectionStatus() and terminating the entire account connection.

Key Changes

  1. Scope Quota Signal to Per-Model Providers (open-sse/services/accountFallback.ts):
    Replaced !isCompatibleProvider(provider) with !hasPerModelQuota(provider, _model) when returning creditsExhausted: true. Built-in passthrough providers, compatible providers, and multi-model configurations now avoid emitting connection-wide creditsExhausted. Account-level balance exhaustion (e.g. Moonshot empty wallet) continues to terminate the whole connection as expected.
  2. Handle Model Quota Exhaustion in markAccountUnavailable (src/sse/services/auth.ts):
    Added isModelLevelQuotaOrRateLimit check so that non-permanent quota/credit exhaustion (RateLimitReason.QUOTA_EXHAUSTED or creditsExhausted) triggers model-only lockout with reason: "quota_exhausted". Sibling models on the same connection remain active and eligible.
  3. Unit Tests (tests/unit/auth-passthrough-credit-lockout.test.ts):
    • Verified b.ai HTTP 400 credit insufficient balance locks only the requested model (glm-5.3-flash), connection remains active, and free model (qwen3.8-flash) remains unlocked.
    • Verified compatible providers continue to lock only the model.
    • Verified single-model providers (e.g. openai) still terminate the connection with credits_exhausted.

Testing

  • Ran unit tests:
    node --import tsx/esm --test tests/unit/auth-passthrough-credit-lockout.test.ts tests/unit/auth-ollama-cloud-per-model-403-3027.test.ts tests/unit/account-fallback.test.ts tests/unit/auth-account-fallback.test.ts
    All passed cleanly.
  • ESLint and Prettier checks passed with 0 errors.

…tion

When a provider with passthrough models (e.g. b.ai, third-party aggregators) returns HTTP 400 with 'credit insufficient balance' or quota exhaustion on a specific paid model, markAccountUnavailable previously treated it as a connection-wide terminal credits_exhausted error because status was 400 (not 404/429/500) and checkFallbackError was only exempting compatible providers instead of all per-model-quota providers.

This commit:
1. Replaces !isCompatibleProvider(provider) with !hasPerModelQuota(provider, _model) in checkFallbackError so passthrough providers do not emit connection-wide creditsExhausted: true.
2. In markAccountUnavailable, checks isModelLevelQuotaOrRateLimit on per-model-quota providers so non-terminal quota/credits exhaustion is handled via model-only lockout instead of poisoning the entire connection.
3. Adds unit tests validating b.ai credit insufficient balance keeps the connection active while locking out only the requested model.
@diegosouzapw

Copy link
Copy Markdown
Collaborator

Thanks for tracking down the passthrough-vs-terminal scoping bug — the reproduction and
your three new tests (bai passthrough, compatible-nickname passthrough, and the
single-model openai regression guard) are solid and all pass against the real
markAccountUnavailable() path with a live SQLite DB. Before merging I'd like two small
additions: (1) a changelog fragment under changelog.d/fixes/, and (2) one more test
case for a *-compatible-* provider WITHOUT the connection-level passthroughModels
override, since removing the old !isCompatibleProvider guard changes the cooldown path
for that case too (fixed 1h instead of the prior exponential backoff) and it'd be good to
pin that as intentional. Nothing structural — happy to merge once those land.

…ion without connection override

Add a changelog fragment for the passthrough credit-lockout fix and a
regression test proving checkFallbackError() applies the fixed
COOLDOWN_MS.paymentRequired cooldown (not the previous escalating
backoff) to *-compatible-* provider nicknames even when the connection
has no explicit providerSpecificData.passthroughModels override, since
isCompatibleProvider() already matches on the nickname prefix alone.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
diegosouzapw and others added 9 commits September 23, 2026 23:29
…through lockout

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
… (codex/gemini/github)

Only passthrough/aggregator and *-compatible-* providers get the model-scoped
credit lockout; providers with a per-model lock only keep the account-level
credits_exhausted semantics.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Keeps the 'credits' lockout reason for per-model 402s; the widened model-scoped
gate only applies to other quota/credit classifications. Ceiling for auth.ts
rebaselined to the measured post-commit value.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
# Conflicts:
#	config/quality/file-size-baseline.json
Keeps src/sse/services/auth.ts within its frozen file-size ceiling after merging the tip; the
PR's rebaseline entry is dropped. No behavior change.
@diegosouzapw
diegosouzapw merged commit e9e4815 into cheaper-inference:release/v3.8.51 Sep 29, 2026
3 of 7 checks passed
diegosouzapw added a commit that referenced this pull request Sep 29, 2026
…ge wave (#15052)

Base-red drain after the 2026-09-28/29 release-drain wave: one production defect (compression preview fuzzy toggle was a no-op since #14983), three intentional contract changes propagated (#14848, #13548, #13487 inventory site), two cross-test state leaks fixed with a refusal-memory reset. Each red reproduced on the tip and green with the fix.
diegosouzapw added a commit that referenced this pull request Sep 29, 2026
… 4) (#15109)

Release-captain base-red fix (v3.8.51 release PR #11442, unit shards 3-4): one production defect (proxyLogger pulled into every settings→proxies import and queried the DB at import time; helper extracted to src/lib/proxyLogHost.ts) and seven contract propagations from #14732, #15044, #15067, #13548, #14117×#14844, #12810. 74/74 across the seven files, 176/176 proxy-log neighbours, typecheck clean.
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