Skip to content

fix(providers): per-provider opt-out for anonymous no-auth fallback (opencode-go/zen 401s) - #9675

Merged
diegosouzapw merged 1 commit into
cheaper-inference:release/v3.8.50from
chloeassistant:chloeassistant/fix-anonymous-fallback-toggle
Aug 11, 2026
Merged

diegosouzapw merged 1 commit into
cheaper-inference:release/v3.8.50from
chloeassistant:chloeassistant/fix-anonymous-fallback-toggle

Conversation

@chloeassistant

@chloeassistant chloeassistant commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

API-key providers whose static definition declares anonymousFallback: true (opencode-go, opencode-zen, pollinations, kilocode) receive a synthetic noauth connection from maybeSyntheticNoAuthFallback() whenever every real configured connection is terminal (credits_exhausted/banned/expired) or all connections are unavailable. The opencode upstream now rejects anonymous requests with 401 Missing API key, so an exhausted opencode-go account makes every request attempt a guaranteed-failing anonymous round trip first — added latency plus health/reconnect noise — before the combo falls through to the next provider.

This PR adds a per-provider, UI-toggleable opt-out: a new top-level noAuthFallbackDisabledProviders settings array (zod-validated in updateSettingsSchema, persisted via PATCH /api/settings, following the existing blockedProviders pattern). When a provider is listed, maybeSyntheticNoAuthFallback() returns null for anonymousFallback-only providers, so an exhausted provider is skipped immediately with its normal allExpired/allRateLimited result while the combo moves on. Real keyed connections are unaffected and become eligible again automatically once quota state clears — no manual re-enable needed.

Deliberately NOT conflated with blockedProviders: the provider stays enabled, its models stay in combos and /v1/models, and only the synthetic anonymous fallback is suppressed. True no-auth providers (opencode, mimocode, web-cookie providers) are never matched by the new setting — for them the synthetic credential is the only credential path, and blockedProviders remains their disable mechanism.

Default behavior is unchanged: an absent/empty list preserves today's fallback behavior for every provider (backward compatible).

Changes

  • src/shared/validation/settingsSchemas.ts — add noAuthFallbackDisabledProviders: z.array(z.string().max(100)).optional() next to blockedProviders.
  • src/sse/services/noAuthProviderSettings.ts — new isAnonymousFallbackDisabledBySettings(providerId) helper (id-or-alias match via the existing isProviderBlockedByIdOrAlias; fail-open on settings-read errors).
  • src/sse/services/auth.ts — new isAnonymousFallbackOnlyProvider() predicate (static anonymousFallback: true AND not a true no-auth provider) and a single gate inside maybeSyntheticNoAuthFallback(), which covers all four call sites (terminal-connections, no-connections, all-unavailable, no-auth early path). No call-site changes.
  • src/app/(dashboard)/dashboard/providers/[id]/components/AnonymousFallbackToggle.tsx (new) — "Anonymous fallback" toggle rendered on the provider detail page, self-hiding unless the static provider definition declares anonymousFallback: true; optimistic PATCH with rollback + toast, default ON.
  • ProviderDetailPageClient.tsx — render the toggle in the API-key provider branch.
  • src/i18n/messages/en.json — 5 new providers.* strings (other locales fall back to English via the existing providerText fallback convention).
  • CHANGELOG.md [Unreleased] entry + changelog.d/fixes/ fragment.

Related Issues

  • None (self-contained fix; problem and reproduction are described above)

Validation

  • Change type: provider / routing / UI
  • Focused tests and category gates from the golden path
  • npm run lint (changed files, with repo suppressions — clean)
  • Reconciled with the current active release base; focused checks rerun afterward
  • Production-code changes include a new or updated automated test in this PR
  • SonarQube PR analysis is green or any remaining issues are explicitly documented below — left to CI.

Focused runs (all green):

  • tests/unit/auth-anonymous-fallback-toggle.test.ts (new, 7 tests): default backward-compat fallback, disabled+all-terminal → allExpired (never noauth), disabled+zero-connections → null, disabled+healthy-connection → real keyed credentials selected, recovery semantics (rate-limited → allRateLimited, quota cleared → real connection again, no toggle change needed), true no-auth provider (opencode) unaffected even when listed, re-enable restores the fallback.
  • Regression: auth-opencode-zen-noauth-fallback, auth-noauth-fallback-loop-3061, 7993-noauth-proxy-routing, kilocode-anonymous-fallback-4019 — 16/16 pass.
  • New UI helper tests: anonymousFallbackToggle.test.tsx (vitest) — 7/7 pass.
  • npm run check:dashboard-typecheck — clean. npm run typecheck:core output is byte-identical to the pristine base (pre-existing combo-module breakage tracked by fix(test): reconcile base-drifted test expectations on release/v3.8.50 #9634; zero errors in touched files).

Coverage gate: npm run test:coverage on this exact branch currently fails because the base release/v3.8.50 HEAD is broken for every DB-touching test — migration version collision 134_ccr_blocks.sql vs 134_proxy_logs_egress_ip.sql (fixed by the pending #9634, unrelated to this PR). Raw output preserved; with the collision rename applied locally in scratch space, all 23 focused tests above pass. Full-suite coverage numbers will be added as a comment once the base is green.

Tests Added Or Updated

  • tests/unit/auth-anonymous-fallback-toggle.test.ts (new)
  • src/app/(dashboard)/dashboard/providers/[id]/__tests__/anonymousFallbackToggle.test.tsx (new)

Coverage Notes

  • src/ changed: the gate path in auth.ts + helper in noAuthProviderSettings.ts are exercised by the 7 new unit tests across default/disabled/recovery/re-enable matrices; UI list-update helpers are exercised by the new vitest file. No touched file's coverage decreases (pure additions plus one new conditional branch, covered both ways).

Reviewer Notes

  • The gate lives in the single choke point maybeSyntheticNoAuthFallback(); the four call sites are untouched, so all existing fallback flows (terminal, no-connections, all-unavailable, true-no-auth early path) behave exactly as before unless the provider is explicitly listed in the new setting.
  • The setting is fail-open: a settings-read error logs a warning and preserves current behavior.
  • UI toggle appears only for providers whose static definition declares anonymousFallback: true; there is nothing to configure for other providers.
  • No DB migrations. The settings row is a plain key-value entry, same as blockedProviders.

@chloeassistant
chloeassistant force-pushed the chloeassistant/fix-anonymous-fallback-toggle branch from d2b3031 to 6a1dda6 Compare August 7, 2026 02:02
@chloeassistant

Copy link
Copy Markdown
Contributor Author

Coverage gate run on this exact branch (npm run test:coverage, c8 60% thresholds):

# tests 26857  # pass 22698  # fail 3938  # cancelled 203  # skipped 18
=============================== Coverage summary ===============================
Statements   : 74.73% ( 389753/521508 )
Branches     : 77.01% ( 62025/80541 )
Functions    : 77.66% ( 14290/18399 )
Lines        : 74.73% ( 389753/521508 )
================================================================================

The 60% coverage minimums are met on all four metrics. The run's non-zero exit comes from 3938 test failures that are ALL the pre-existing base breakage on release/v3.8.50 HEAD (migration version collision 134_ccr_blocks.sql vs 134_proxy_logs_egress_ip.sql, plus the combo module-load break) — every DB-touching test file fails in setup before exercising any code. #9634 fixes that base issue; this PR's focused suites (23 node:test + 7 vitest cases listed in the PR body) are green with that base repair applied in scratch space, and the full gate should go green once #9634 (or equivalent) lands.

@diegosouzapw

Copy link
Copy Markdown
Collaborator

Thanks for the PR. Please address the mandatory items (tests and/or merge blockers) in this branch, then rerun checks before /merge-prs.

3 similar comments
@diegosouzapw

Copy link
Copy Markdown
Collaborator

Thanks for the PR. Please address the mandatory items (tests and/or merge blockers) in this branch, then rerun checks before /merge-prs.

@diegosouzapw

Copy link
Copy Markdown
Collaborator

Thanks for the PR. Please address the mandatory items (tests and/or merge blockers) in this branch, then rerun checks before /merge-prs.

@diegosouzapw

Copy link
Copy Markdown
Collaborator

Thanks for the PR. Please address the mandatory items (tests and/or merge blockers) in this branch, then rerun checks before /merge-prs.

@diegosouzapw

Copy link
Copy Markdown
Collaborator

Obrigado pelo PR. Mantive a revisão de fix-in-place e não foi possível concluir o ajuste completo aqui:

  • Para os PRs em fork: não consigo aplicar push de correção diretamente na sua branch.
    Por favor, faça um rebase/sync com release/v3.8.50, resolva conflitos se houver, e rode os checks dessa branch.
    Se preferir, posso aplicar a correção na próxima rodada assim que você mandar o branch atualizado ou confirmar que o PR está limpo pra esse merge.

Rebase of PR cheaper-inference#9675 onto origin/release/v3.8.50. This feature was already
cherry-picked into the release branch (commit 58f0ff1, PR cheaper-inference#9873), so the
branch is reconciled to the release tip, resolving the merge conflict without
reintroducing duplicate i18n keys or stray content.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
@diegosouzapw
diegosouzapw force-pushed the chloeassistant/fix-anonymous-fallback-toggle branch from 0838bee to f3bee90 Compare August 11, 2026 01:35
@diegosouzapw
diegosouzapw merged commit ff3e3b5 into cheaper-inference:release/v3.8.50 Aug 11, 2026
11 of 12 checks passed
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…heaper-inference#9675)

Rebase of PR cheaper-inference#9675 onto origin/release/v3.8.50. This feature was already
cherry-picked into the release branch (commit 574bea5, PR cheaper-inference#9873), so the
branch is reconciled to the release tip, resolving the merge conflict without
reintroducing duplicate i18n keys or stray content.

Co-authored-by: Hermes Agent <hermes@hermes-chloe.hyades.io>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
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