Skip to content

fix(sse): skip already-refused route per request on 429 - #13795

Merged
diegosouzapw merged 8 commits into
diegosouzapw:release/v3.8.51from
maxmad64bis:feat/n90-dedup-cid
Sep 16, 2026
Merged

diegosouzapw merged 8 commits into
diegosouzapw:release/v3.8.51from
maxmad64bis:feat/n90-dedup-cid

Conversation

@maxmad64bis

@maxmad64bis maxmad64bis commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A request can be re-sent to a route the upstream just refused with a 429, delaying the surfaced upstream error. The dispatch loop now records each refused route for the running request and moves on, so a refused route is tried once per request. Fully excluded requests return the last upstream error through the existing drain path.

Related Issues

Validation

  • Change type: provider
  • Focused tests and category gates from the golden path
  • npm run lint
  • Reconciled with the current active release base; focused checks rerun afterward
  • Production-code changes include a new or updated automated test in this PR

Tests Added Or Updated

  • tests/unit/opencode-429-proxy-dedup.test.ts (3 tests green: refused route tried once, fully excluded request drains last error, distinct routes tried twice)

Coverage Notes

  • Change in open-sse/executors/opencode.ts covered by the new test (refused-route record on the 429 arm, fail-open drain, no over-exclusion).

Reviewer Notes

  • Smallest diff that closes the gap: comment + key write + enriched log on the 429 arm only (open-sse/executors/opencode.ts:585-587,699-707); predicate, guard, drain and cooldowns byte-identical. A refused direct route records nothing. No flag: the change only stops re-serving refused routes, it adds no retry wave. Coordination with open fix(sse): stop retry wave on rate-limited 429 and drain 429 once #13657 (same arm, no code dependency either way — a recorded key before a possible break is a dead write): suggested order is landing this first. API Route Typecheck red is inherited (src/lib/db/callLogStats.ts TS2300, untouched by this PR — same failure on base and on sibling fix(sse): stop unhydrated compatible connections routing to the real OpenAI/Anthropic API (#13452) #13798). Follow-up idea (not in scope): tighten the fully-excluded assertion to an exact count and add a direct-route case.

@maxmad64bis
maxmad64bis force-pushed the feat/n90-dedup-cid branch 2 times, most recently from 49f9679 to 890ab2f Compare September 15, 2026 20:52
@maxmad64bis maxmad64bis changed the title fix(sse): dedup already-429 proxies per request fix(sse): skip already-refused route per request on 429 Sep 15, 2026
@maxmad64bis
maxmad64bis marked this pull request as ready for review September 15, 2026 20:57
A request never re-sends to a route the upstream just refused with 429:
record the refused route key in the request-local tried set on the 429
arm (mirror of the 5xx/geo arms). Fail-open preserved via the existing
fallback; a refused direct route (null key) records nothing.
maxmad64bis and others added 7 commits September 15, 2026 20:58
…ailures (diegosouzapw#13615)

Behind the new `OPENCODE_TRANSIENT_FAILOVER_BACKOFF` flag (default off), after two consecutive transient upstream failures the opencode rotation pauses before each later account (1.5s, 3s, 6s, capped at 10s per request) instead of hammering the upstream.

Maintainer rework before merge (kept the idea, no default behavior change):
- The pause honors the client abort signal (no dispatch after a disconnect), the failed attempt's body is cancelled before sleeping, `transientRetryDelayMs` now uses its arguments, and the sleep is injectable so the tests run without real timers.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
…o clear auth failure (diegosouzapw#13609)

Behind the new `MISTRAL_AMBIGUOUS_401_SOFT_LOCKOUT` flag (default off), a bare Mistral 401 (`{"detail":"Unauthorized"}`, identical for a revoked key and an exhausted quota) gets a retryable cooldown instead of parking the connection as `expired`; after three soft strikes within an hour the next bare 401 parks it, so revocation still converges.

Maintainer rework before merge (kept the idea, no default behavior change):
- The predicate is shared with the connection-test module instead of duplicated; the squeezed 139-char line that dodged the file-size gate is formatted normally and the growth is rebaselined honestly with an annotation.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
…d ERROR_TYPE_CONTRACT import left by the batch merges (diegosouzapw#13816)

Merged with admin on local + CI evidence: `tests/unit/i18n-catalogs-no-duplicate-keys.test.ts` red on the tip (59 catalogs) → `pass 3 / fail 0` here; **API Route Typecheck passes on this PR** (it fails on every PR based on the current tip because of the duplicated `ERROR_TYPE_CONTRACT` import this removes); CodeQL, semgrep, Vitest fast-path, Docs gates, Change Classification pass. The remaining red checks (Fast Quality Gates, Merge integrity, Unit Tests fast-path 1/2/4) are the same inherited tip reds every PR on release/v3.8.51 shows right now — diegosouzapw#13747 sweeps them. Both removed lines were byte-identical duplicates; nothing parsed or typed changes.
…gosouzapw#13657)

The opencode executor classifies rate-limited 429 bodies (`classify429`, with real tests) and, when a whole account wave is exhausted, returns the last real upstream 429 — status, body, `Retry-After` and quota headers intact — so the provider error rules (monthly-quota cooldown) keep working.

Maintainer rework before merge (kept the idea, no default behavior change):
- The original stopped the cross-account wave at the first classified 429 and replaced the response with a synthetic one that dropped the body and headers; stopping early is now opt-in behind `OPENCODE_RATE_LIMITED_429_EARLY_STOP` (default off), the rate-limited account is still cooled down, the body is read as a bounded 8 KiB prefix from a clone and the original is never consumed, and the unused `status` input is gone.

Validated first on the combined board of all 38 PRs of this batch (10 merged as-is, 28 after the maintainer rework) on top of release/v3.8.51 c0f92ec: typecheck:core, check:open-sse-typecheck and check:dashboard-typecheck clean; ESLint clean on every changed file; file-size (rebaselined for the combined growth), complexity, cognitive-complexity, changelog-integrity, docs-counts, docs-sync, migration-numbering and i18n new-key gates green; 735 focused node:test cases with the only batch-caused failure (a flag-count assertion) fixed. Then re-validated alone on the fresh release tip right before this merge: ESLint on the changed files, typecheck:core, check:open-sse-typecheck, the file-size/complexity/changelog gates and this PR's own tests.

Thanks @maxmad64bis!
…(stryker, CLI i18n, paid-target fixture, call-log traceId, Jina prefix, callLogStats import, gitleaks) (diegosouzapw#13747)

* fix(ci): clear the release/v3.8.51 base-reds left by the 09-15 batch — stryker coverage, CLI ready_timeout key, paid-target fixture, call-log traceId, Jina custom prefix

Every PR into release/v3.8.51 pushed after diegosouzapw#13635/diegosouzapw#13678 still failed Fast
Quality Gates and all four Unit fast-path shards on the same 16 tests. Each one
reproduces on the pure tip; none is a product defect:

- mutation-test-coverage: noauth-model-lockout and
  local-token-budget-429-skips-cooldown (diegosouzapw#13606) were missing from
  stryker.conf.json tap.testFiles.
- cli-i18n-catalog: --ready-timeout calls t("serve.ready_timeout") with no
  catalog entry; added to en, zh-CN and zh-TW (the parity-checked locales).
- paid-model-target(-routes)-6540: diegosouzapw#13407 removed Together's one-time credit
  from the free catalog, so "together/..." classifies as unknown and the
  save-time guard correctly lets it through. Fixture is now
  gemini/gemini-3.1-pro-preview, plus a precondition test on the fixtures.
- attempt-logging-early-keepalive-merge / video-bridge-log-redaction: diegosouzapw#13546
  keys the call-log row on traceId; baseCtx now defaults traceId to
  pendingRequestId (same pattern as chatcore-attempt-logging). The keepalive
  test also moves to the 30s wall-clock poll deadline video-bridge uses.
- models-catalog-route: custom Jina rows keep the jina-ai/ prefix; diegosouzapw#13403
  changed the custom assertion to jina/ (only synced rows use the alias).

Refs diegosouzapw#12732

* fix(ci): clear the four reds the first r4 CI run surfaced — callLogStats duplicate import, Uzbek gitleaks false positive, redaction probe traceId, file-size

- src/lib/db/callLogStats.ts: the diegosouzapw#13641 merge left ERROR_TYPE_CONTRACT
  imported twice (TS2300), failing API Route Typecheck and
  check:dashboard-typecheck on every PR.
- .gitleaks.toml: the Uzbek catalog from diegosouzapw#13727 translates outputTokenDesc as
  "Yakunlash/javob tokenlari"; generic-api-key reads it as a token value.
- dashboard-request-failed-redaction-probe: reads the persisted row by
  traceId (diegosouzapw#13546); with pendingRequestId it asserts null.
- models-catalog-route: drop the explanatory comment, which pushed the frozen
  file over its size cap; the rationale lives in the changelog fragment.

Refs diegosouzapw#12732

* fix(ci): re-freeze the two test files diegosouzapw#13748/diegosouzapw#13749 grew past their file-size caps

PR-mode check:file-size relaxes source files against the base but not
testFrozen, so image-generation-handler.test.ts (2133->2235, diegosouzapw#13748) and
batch_api.test.ts (1345->1348, diegosouzapw#13749) failed Fast Quality Gates on every PR,
this one included. Caps set to the merged LOC, with the justification entry.

Refs diegosouzapw#12732

* fix(ci): register free-badge-provider-gate (diegosouzapw#13645) in stryker tap.testFiles

diegosouzapw#13645 landed a covering test for src/sse/services/auth.ts without the
stryker entry, so the strict mutation-test-coverage gate went red again.

Refs diegosouzapw#12732

* fix(ci): clear two more base-reds the diegosouzapw#13440/diegosouzapw#13439 merges added

- stryker.conf.json: register daily-reset-tz-threading (diegosouzapw#13440), which covers
  accountFallback.ts and rrState.ts.
- .gitleaks.toml: allowlist the PROTECTED_PRIORITY_INFRA_502_ENABLED flag id
  (diegosouzapw#13439); generic-api-key reads its key: as a token (secrets ratchet 0 -> 1).

Refs diegosouzapw#12732

* docs(changelog): tidy the stryker base-red fragment wording

Refs diegosouzapw#12732
…; ratio gate now blocking (diegosouzapw#13782)

PR-4 of the locale-expansion plan. 215,363 strings retranslated across the 65 catalogs with the new `sync-ui-keys --retranslate-identical`; the share of leaves still identical to English drops from a mean of 18.3 % to 1.8 % (Spanish 56 → 2.1). No `__MISSING__` marker or missing key is left; zh glossary normalised; pinned product/flag names kept English and allowlisted. Baseline tightened and the CI step `i18n real-translation ratio` is blocking from here on.

⚠️ base-red inherited: diegosouzapw#12732
@diegosouzapw
diegosouzapw merged commit d4835c5 into diegosouzapw:release/v3.8.51 Sep 16, 2026
3 of 7 checks passed
@maxmad64bis
maxmad64bis deleted the feat/n90-dedup-cid branch September 23, 2026 00:31
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…#13795)

On a 429 the opencode executor now records the refused proxy's key in the request-local tried-set, exactly like the 403/451, 5xx, stall and network arms already did — so a second account sharing that same proxy is not dialed and refused again before the loop reaches a genuinely different route (direct, or another proxy).

Reviewed against the tip that already carries your 38 merges from this evening: this is additive to the flags that landed today (`OPENCODE_RATE_LIMITED_429_EARLY_STOP`, `PROXY_SKIP_RECENTLY_FAILED`, `OPENCODE_USER_BLOCKED_ROTATION`, `OPENCODE_TRANSIENT_FAILOVER_BACKOFF`) and does not double-skip when combined with them; direct accounts have a null proxy key and correctly record nothing.

Validated as a combined board first (this PR merged with the 11 siblings of the same batch on the release tip): eslint on every changed file with the suppressions file, typecheck:core, check:open-sse-typecheck, complexity, cognitive-complexity, changelog-integrity, i18n new-key coverage, docs-sync, migration-numbering, provider-consistency and a duplicate-identifier audit all green, plus 275 passing / 0 failing focused node:test cases across the 28 test files the batch touches. Then re-validated alone on the fresh tip before this merge: conflicts re-resolved, file sizes rebaselined for this PR's own growth, eslint and this PR's focused tests re-run.

Thanks @maxmad64bis!
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