Repository navigation
fix(guardrails): stop Vision Bridge from re-selecting a model locked after a 404 (#12111) - #13259
Merged
Merged
Conversation
7 tasks done
Githab-capibara
added a commit
to Githab-capibara/OmniRoute
that referenced
this pull request
Sep 17, 2026
…after a 404 (diegosouzapw#12111) (diegosouzapw#13259) Merged as part of the 39-PR owner batch of 2026-09-11, validated as a unit. Boarded into one consolidated worktree cut from `release/v3.8.51` with the other 38 — zero conflicts between them. - ESLint over every changed file: no errors (the only finding was one suppression entry the batch emptied, pruned on diegosouzapw#13243) - `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, within baseline); `check:changelog-integrity` OK - complexity 2821 / baseline 3218 and cognitive-complexity 1272 / baseline 1437 — both under baseline - 256 assertions green: 246 under node:test and 10 under vitest, which is where `tests/unit/**/*.test.tsx` actually runs - `check-file-size`: `chatCore.ts` rebaselined 6144 → 6146 for diegosouzapw#13278 and diegosouzapw#13276, annotated and landed on diegosouzapw#13243⚠️ base-red inherited: diegosouzapw#12732 — the provider count (356 in the docs vs the 358 the modules define) and `open-sse/utils/stream.ts` at 3115 > frozen 3098 both reproduce on the pure tip with zero contribution from this batch.
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…after a 404 (diegosouzapw#12111) (diegosouzapw#13259) Merged as part of the 39-PR owner batch of 2026-09-11, validated as a unit. Boarded into one consolidated worktree cut from `release/v3.8.51` with the other 38 — zero conflicts between them. - ESLint over every changed file: no errors (the only finding was one suppression entry the batch emptied, pruned on diegosouzapw#13243) - `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, within baseline); `check:changelog-integrity` OK - complexity 2821 / baseline 3218 and cognitive-complexity 1272 / baseline 1437 — both under baseline - 256 assertions green: 246 under node:test and 10 under vitest, which is where `tests/unit/**/*.test.tsx` actually runs - `check-file-size`: `chatCore.ts` rebaselined 6144 → 6146 for diegosouzapw#13278 and diegosouzapw#13276, annotated and landed on diegosouzapw#13243⚠️ base-red inherited: diegosouzapw#12732 — the provider count (356 in the docs vs the 358 the modules define) and `open-sse/utils/stream.ts` at 3115 > frozen 3098 both reproduce on the pure tip with zero contribution from this batch.
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.
Closes #12111
Summary
The Vision Bridge auto-router could keep selecting a model that
was already model-locked after a 404, because
getVisionCapableModels()(
src/lib/guardrails/visionBridgeRouter.ts) only filtered candidates on theregistry
supportsVisionflag and connection-scoped credential usability(
hasUsableCredentialsForModel) — it never consulted the per-connectionmodel lockout that
open-sse/services/accountFallback.tssets whenchatCore.tslocks a model for 120s after a 404 "model not found" (theconnection itself stays active, so the credential check never saw it). The
60s selection cache (
cachedModelRemainsAvailable) had the same gap, so apick that became locked mid-cache-window kept being served for up to another
60s of failing requests.
Root cause
isModelLocked(provider, connectionId, model)is scoped perprovider+connection+model, so the fix can't just drop a model when it's
locked on some connection — it has to enumerate the provider's usable
connections and exclude the model only when it is locked on every one of
them (mirrors
isConnectionEligibleForModelinopen-sse/services/autoCombo/resilienceCandidateFilter.ts, which alreadysolves this for auto-combo pools).
Fix
src/lib/guardrails/visionBridgeCredentials.ts: addedgetUsableConnectionsForModel(), a connection-aware sibling ofhasUsableCredentialsForModel()that returns the actual usableprovider_connectionsrows for a model's provider (reusing the existinggetProviderConnections+isProviderConnectionUsable/hasTerminalConnectionStatuslogic) instead of collapsing to a boolean.src/lib/guardrails/visionBridgeRouter.ts:isModelUsableGivenLockouts()checks a model against every connectionthat could serve it — DB-known usable connections plus any connectionId
already carrying an active lockout for that provider (a 404 lock can
target a connectionId the DB lookup doesn't independently surface) —
and excludes the model only when all of them are locked. Fails open
when nothing is known about the provider's connections, matching
hasUsableCredentialsForModel's existing fail-open contract.getVisionCapableModels()now runs this check after the existingcredential filter.
cachedModelRemainsAvailable()now also re-checks the lockout, so the60s selection cache drops a pick that becomes locked mid-window instead
of continuing to serve it.
VisionBridgeRouterDepsgained an injectableisModelLockedhook(defaulting to the real
accountFallback.isModelLocked), following thesame DI pattern already used for
hasUsableCredentials/getActiveSyncedCatalog—node:testhas no supported ESMmodule-mocking mechanism.
Regression tests
tests/unit/guardrails/visionBridge12111Repro.test.ts— the TDD reprofrom the analysis plan, reproducing the reporter's exact setup
(
lockModel("nvidia", "conn-nvidia-1", ..., "not_found", 120000)thenasking
getBestVisionModel()for a pick with only nvidia credentialed).The plan's original model id (
moonshotai/kimi-k2.6) no longer exists inthe nvidia registry (renamed to
kimi-k3since the analysis) — updated tothe current id, which resolves the same way (first vision-capable nvidia
model in registry order).
RED (before the fix):
GREEN (after the fix): passes.
tests/unit/guardrails/visionBridgeRouter.test.ts— 3 new cases addedalongside the existing 21 (all still pass):
connection on the same provider stays usable (the multi-connection
risk called out in the analysis)
Full suite:
node --import tsx/esm --test tests/unit/guardrails/visionBridgeRouter.test.ts tests/unit/guardrails/visionBridge12111Repro.test.ts→ 25/25 pass.Gates run
node scripts/check/check-file-size.mjs— OKnode scripts/check/check-complexity.mjs— OK (2798 violations vs baseline 3218)node scripts/check/check-cognitive-complexity.mjs— OK (1265 vs baseline 1437)npm run typecheck:core— exit 0npx eslint --suppressions-location config/quality/eslint-suppressions.json <changed files>— exit 0, no outputnode scripts/check/check-changelog-integrity.mjs— OK