fix(combos): cool a permanently-failed target for ten minutes, not sixty seconds - #5859
vadymhimself wants to merge 1 commit into
Conversation
|
✅ Deterministic PR hygiene checks passed. |
✅ READY
Review readiness checklist
✅ 4/4 boxes ticked. This pull request is already Ready for Review. |
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Important Review skippedReview was skipped as selected files did not have any reviewable changes. ⚙️ Run configurationConfiguration used: Repository: lidge-jun/opencodex/.coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: lidge-jun/opencodex/.coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review. 📝 WalkthroughWalkthrough
ChangesProvider-Scoped Cooldowns
Priority: ➖ Normal Estimated code review effort: 2 (Simple) | ~10 minutes Change: Bug fix Merge Risk: ⚪ Minimal · up to The cooldown change has no identified merge-blocking risk. It is ready to merge after normal checks. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to Credential and billing failures now keep affected combo targets unavailable for up to ten minutes instead of one minute. Existing failover and deadline rules remain in place, and no security regression was confirmed. The longer wait can delay reuse after a credential is repaired. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
리뷰 · 우선순위 46 / 80이 PR은 콤보가 망가진 키를 다시 보내는 간격을 늘립니다. 키가 거절되거나 결제가 안 된 실패가 오고, 서버가 기다릴 시간을 안 주고, 운영자도 cooldownMs를 안 적으면, 지금은 60초 뒤에 그 대상을 다시 부릅니다. 그 상태는 1분 안에 풀리지 않습니다. 본문 측정은 2시간 동안 401 invalid_api_key만 131번 나갔고, 속도는 한 시간에 64.7번이었습니다. 호출한 쪽은 다음 대상으로 넘어가 109번은 성공으로 보였습니다. 남는 비용은 헛된 전송과 지연입니다. 바뀌는 곳은 대기 시간의 마지막 칸뿐입니다. 코드가 invalid_api_key, insufficient_quota, subscription_required, payment_required, billing_error, insufficient_balance이면 10분을 기다립니다. Retry-After, 리셋 시각, 운영자가 적은 cooldownMs는 그 10분보다 앞에 있습니다. 요청량 코드 1302와 1305는 5초, 나머지 실패는 60초입니다. 쉬는 범위는 그대로입니다. 이 코드는 이미 그 제공자 전체를 쉬게 하므로, 10분은 같은 제공자의 다른 모델에도 붙습니다. 실패하면 다음 대상으로 넘어가는 동작은 그대로입니다. 베이스는 dev입니다. 아직 드래프트이고, 본문 체크리스트 네 칸은 비어 있습니다. src/types/config.ts:1237 - cooldownMs 주석은 값을 안 적으면 요청량 429(1302, 1305)는 5초, 그 외는 60초라고 적습니다. 위 여섯 코드는 이제 10분입니다. docs-site/src/content/docs/guides/combos.md:327 과 334행의 우선순위 문장도 마지막이 60초입니다. PR 본문 Why duration only - 테스트가 scope를 target으로 고정한다고 적혀 있습니다. tests/codex-integration/combo-permanent-failure-cooldown.test.ts:40 은 provider를 기대합니다. failover.ts의 comboFailureCooldownScope도 이 코드에 provider를 돌려줍니다. 메인테이너의 판단이 필요한 지점 10분은 MAX_COOLDOWN_MS 그 값입니다. 로컬 대기의 상한을 나중에 늘리면, 망가진 키의 대기 시간도 같이 늘어납니다. insufficient_quota는 이 여섯에 들어 10분이 됩니다. 사용량 창 1308은 이 PR이 그대로 두며, 60초입니다. failover.ts:540 은 401, 402, 403이면 코드와 관계없이 provider로 쉽니다. 테스트 44행은 코드가 authentication_error인 401을 60초로 고정합니다. 측정에 잡힌 invalid_api_key는 10분입니다. 다른 401 코드도 10분으로 볼지는 별도 결정입니다. 너의 추천 측정한 invalid_api_key 경로에는 이 대기 시간이 맞습니다. 머지 전에 config.ts 주석과 guides/combos.md의 대기 문장에 여섯 코드의 10분을 적으세요. 본문의 scope 문장은 provider로 고치면 됩니다. 체크리스트는 그 다음에 채우면 됩니다. 닫을 중복 PR은 없습니다. 이 댓글은 grok-bot이 작성했습니다 |
A target whose credential is rejected or whose bill is unpaid cannot succeed on the next attempt, or on any attempt until a human intervenes. Without a stated deadline it fell through to `DEFAULT_COOLDOWN_MS`, so the combo re-offered it every 60 seconds and burned a doomed request each time. The six codes in `PROVIDER_SCOPED_FAILURE_CODES` are already recognised as permanent elsewhere in this file -- `comboFailureCooldownScope` returns "provider" for exactly this set -- so the cooldown chain now reads the same roster and picks `MAX_COOLDOWN_MS` instead. Duration only. The scope and hop decisions are byte-identical to before: `isProviderScopedQuotaCap`, `comboFailureCooldownScope`, `comboFailureDecision` and `isTransientRequestRateLimit` are untouched, and a failing credential still blacks out the whole provider and still hops. Every more specific signal keeps priority because it sits earlier in the `??` chain: a server `Retry-After`, a quota reset, and an operator's explicit `cooldownMs` all still win outright. Unrelated failures keep the 60-second default and request-rate 1302 keeps its short one. The new test file pins all four of those properties, and fails on the six codes without this change. Co-Authored-By: Claude Code <noreply@anthropic.com>
6d775dd to
8a58d16
Compare
…cted credentials Salvages #5894's fr/tr/zh-tw guide lines and its structure/runtime.md sentence (extended to the #5859 credential and billing codes, same line count), and adds the credential/billing case to the English guide. Co-authored-by: codingbo <cnsdbo@163.com> Co-authored-by: Vadym O <bolein95@gmail.com>
…-quota.test.ts at its cap Registers combo-codex-exhaustion-cooldown.test.ts (#5874) and combo-permanent-failure-cooldown.test.ts (#5859) in both layout registries, and drops the six comment lines #5868 added to tests/providers/provider-quota.test.ts, which sits at its 3763-line ratchet cap. The assertions are unchanged. Co-authored-by: Vadym O <bolein95@gmail.com>
|
Thanks! This landed on |
Summary
coolComboTargetpicks a cooldown duration from a??chain. When upstream supplies noRetry-Afterand no reset instant, and the operator configured nocooldownMs, the chain ends in a two-way choice:COMBO_REQUEST_RATE_COOLDOWN_MSfor a transient request-rate 429, otherwiseDEFAULT_COOLDOWN_MS— 60 seconds.That default is right for a transient failure and wrong for a permanent one. The codes in
PROVIDER_SCOPED_FAILURE_CODES—invalid_api_key,insufficient_quota,subscription_required,payment_required,billing_error,insufficient_balance— are already treated as provider-scoped, so the target is cooled. But cooling it for 60 seconds re-offers a credential that cannot succeed, once a minute, for as long as the process runs. None of those six conditions resolves itself within a minute.This PR changes only which value that last arm yields: a failure whose code is already in that set takes
MAX_COOLDOWN_MS(10 minutes, already the clamp on this path) instead of 60 seconds.Evidence
Measured 2026-09-24 on a two-target
failovercombo (anthropic+openai), over a 2.02-hour window:invalid_api_key, every one at attempt ordinal 1. Mean 1,259 ms each, 164.9 s of latency spent on sends that could not succeed.hasApiKey=false,models=0,entitlement.status="unavailable"), so no send in that window could ever have succeeded.The reason this survives unnoticed: the combo hops to a healthy target and the request still returns 200. 109 of those 131 requests succeeded from the caller's point of view. The cost is latency and wasted upstream calls, not visible errors.
With this change the same window yields roughly 12 probes/hour instead of 64.7 — enough to notice a repaired credential quickly, without hammering a dead one.
Why duration only
Scope and routing are deliberately untouched.
isProviderScopedQuotaCap,comboFailureCooldownScope,comboFailureDecisionandisTransientRequestRateLimitare byte-identical todev; the only edited expression is the final fallback of the duration chain. A test in the added file asserts the scope staystargetand the decision stayshop, so the duration-only claim is enforced rather than promised.Everything more specific than a guess still outranks the new default, unchanged and earlier in the chain: a server-stated
Retry-After, a reset instant, and an operator's explicitcooldownMs.Ten minutes rather than longer is deliberate — a credential can be repaired at any moment, and recovery must not require a restart.
No new constant and no second roster: the change reuses
MAX_COOLDOWN_MSand the existingPROVIDER_SCOPED_FAILURE_CODES.Scope note
A related case is not included here: the ChatGPT Codex backend reports a spent plan window as HTTP 502
upstream_server_errorcarrying the proseThe usage limit has been reached, whichisProviderScopedQuotaCapcannot match, so an exhausted window also takes the 60-second default. Covering it requires a status-independent prose match, which collides withtests/codex-integration/combos.test.ts→keeps the default cooldown for usage-window 1308. That test states an expectation, so changing it is a design decision rather than a bug fix, and it is raised separately as an issue rather than folded in here.Verification
Regression test added, covering the permanent-credential case, an unrelated 502 still taking the 60-second default, an explicit server
Retry-Afterstill outranking the new default, and the scope/hop invariant above.Proven red before green: reverting only the fallback arm fails the new cases; restoring it passes them.
tests/codex-integration/combos.test.tsandcombo-authoritative-reset.test.tspass unchanged — those pin the server-delay and operator-cooldown behaviour this change must not disturb.typecheck,structure:check,privacy:scanandgit diff --checkare clean.Review readiness checklist
This PR stays in draft until every box below is ticked. Tick all four boxes once the requirements are met:
Required local validation passed; commands, results, and any full-suite exception are documented. —
typecheckclean; new test file 9 pass / 0 fail / 28 expect();combos.test.ts+combo-authoritative-reset.test.ts100 pass / 0 fail / 442 expect();structure:check, file-size ratchet,privacy:scan,git diff --checkall clean. Fullbun run testnot run.I pushed my PR to a recent dev commit (at most 10 behind; a maintainer may still ask for the exact tip before merge). — rebased onto
ca74738bc, now 0 behind.I resolved all correct Codex and CodeRabbit findings. — CodeRabbit review completed with no findings on this PR.
My PR is ready for review.
Summary by CodeRabbit