Skip to content

fix(sse): treat OpenCode free-tier 403 as request-scoped for combo routing - #14675

Merged
diegosouzapw merged 1 commit into
diegosouzapw:release/v3.8.51from
Laksopan23:fix/opencode-freetier-skip-14313
Sep 24, 2026
Merged

diegosouzapw merged 1 commit into
diegosouzapw:release/v3.8.51from
Laksopan23:fix/opencode-freetier-skip-14313

Conversation

@Laksopan23

Copy link
Copy Markdown
Contributor

Summary

Fixes #14313 — OpenCode free-tier 403 (\FreeTierError) was not request-scoped for combo routing, so both lockout arms (first-failure + done-retrying) and provider cooldown could fire from a refusal that is the same verdict on every account for the same request shape.

Changes

  1. *\comboPredicates.ts* — \isRequestScopedUpstreamFailure\ now recognizes \FreeTierError\ (type/code); \isComboRequestScopedFailure\ also matches the relayed body sentence via \isOpencodeFreeTierRefusal\ (403/451).
  2. *\opencodeFreeTierSkip.ts* (new) — short TTL (~3 min) in-process skip keyed by opencode* provider. Written on \PROJECT_ROUTE_ERROR\ when the connection is the synthetic
    oauth\ id and the body is a free-tier refusal; read by
    esilienceCandidateFilter\ (auto-combo noauth candidates) and \maybeSyntheticNoAuthFallback\ (credential selection).
  3. Copy — OpenCode Free \�uthHint/\ reeNote/
    otice\ and \docs/reference/FREE_TIERS.md\ now document that the free tier can only be used from within OpenCode.
  4. Change 1 (refresh skip) was already covered on base by \skipCredentialRefresh.ts\ +
    oauth-refresh-guard.test.ts\ — no production change needed; that test file gained a \clearOpencodeFreeTierSkips\ beforeEach so the new TTL does not leak across cases in the same process.

Tests (TDD)

File What it pins
\ ests/unit/opencode-free-tier-combo-scoped-failure-14313.test.ts\ FreeTierError type/body/451 → scoped; ordinary auth 403 stays unscoped; \shouldSkipConnDisable\ never cools
\ ests/unit/opencode-free-tier-noauth-ttl-skip-14313.test.ts\ TTL skip drops only noauth candidate; expires; foreign providers ignored
\ ests/unit/opencode-noauth-copy-freetier-14313.test.ts\ Catalog copy + FREE_TIERS.md disclose the OpenCode-only gate

Verification

  • Focused unit suite: 42/42 pass (new + related free-tier/lockout/combo classification)

  • pm run test:vitest: 482/482 pass
  • Focused
    px eslint : 0 errors

  • pm run check:cycles: OK

  • pm run check:changelog-integrity: OK
  • Prettier on all changed files: OK

  • pm run typecheck:core: only pre-existing base-red \cliproxyAccountHealth.ts:157\ TS2322

Notes

⚠️ base-red inherited: #14547

Refs #14313

…uting

A FreeTierError refusal is the same verdict on every account for the same request shape, so combo must not record model lockout or connection cooldown from it. Class FreeTierError (type/code) and the relayed body sentence as request-scoped in comboPredicates, pause the synthetic noauth path for a short TTL after such a refusal (opencodeFreeTierSkip), and disclose the OpenCode-only free-tier gate in catalog copy and FREE_TIERS.md.

Refs diegosouzapw#14313
@diegosouzapw

Copy link
Copy Markdown
Owner

Verified the wiring end to end — isOpencodeFreeTierRefusal() is reused (not duplicated)
and already excludes fingerprint/geo-block/user-blocked before matching the free-tier
signals, and the TTL skip is correctly read at both call sites (auth.ts's
maybeSyntheticNoAuthFallback and resilienceCandidateFilter.ts). Good separation from the
geo-block detector. One minor, non-blocking note: isComboRequestScopedFailure calls the
unscoped isOpencodeFreeTierRefusal rather than the provider-scoped wrapper used elsewhere —
low risk given how specific the free-tier text signals are, but worth a look. Looks
merge-ready; expect a trivial merge conflict with #14585 in the same comboPredicates.ts
block depending on merge order.

@diegosouzapw
diegosouzapw merged commit c3fee3d into diegosouzapw:release/v3.8.51 Sep 24, 2026
3 checks passed
gravonyxcloud pushed a commit to gravonyxcloud/OmniRoute that referenced this pull request Sep 29, 2026
…uting (diegosouzapw#14675)

Validated in the round-3 combined board (release/v3.8.51 @ e3b152badd + 59 PRs, gates on the 192.168.0.113 box): typecheck/open-sse/dashboard typecheck, complexity, cognitive, changelog integrity, route-validation, docs-counts, cycles, changed node:test files and vitest green except the inherited base-reds tracked in diegosouzapw#14496/diegosouzapw#14547 and file-size ratchet drift (rebaselined in a follow-up).

Thanks @Laksopan23!
idoomblast added a commit to idoomblast/OmniRoute that referenced this pull request Oct 4, 2026
A 403/451 FreeTierError refusal says nothing about the account or the model:
every sibling account gets the same verdict for the same request, and the
same account answers 200 once the request matches the contract. Handle it
without poisoning account health (upstream diegosouzapw#14011, diegosouzapw#14675, diegosouzapw#14313):

- errorClassifier.ts: classify the refusal as PROJECT_ROUTE_ERROR instead of
  FORBIDDEN (would ban the connection permanently) or GEO_BLOCKED (would park
  a healthy account for 24h).
- auth.ts: zero-cooldown fallback for the refusal; per-model 402 credits keep
  the model-only lockout on passthrough providers (connection stays active);
  skip the synthetic noauth candidate while its short-TTL refusal skip is on.
- comboPredicates.ts: request-scoped classification for combos, both by
  FreeTierError type and by the relayed sentence alone (the parser keeps
  error.type aside, so the body sentence is the reachable signal).
- resilienceCandidateFilter.ts: exclude the skipped synthetic noauth node.
- noauth.ts + FREE_TIERS.md: disclose the client-contract restriction so the
  dashboard does not promise keyless access upstream will refuse.
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.

fix(): Opencode free models no longer working

2 participants