Skip to content

fix(combos): a spent pool account must not cool the whole target (carry #6234) - #6255

Merged
lidge-jun merged 5 commits into
devfrom
codex/rt6-l2-combo-account-cooldown
Sep 29, 2026
Merged

lidge-jun merged 5 commits into
devfrom
codex/rt6-l2-combo-account-cooldown

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Carry fix(combos): a spent pool account must not cool the whole target #6234's two commits so a spent account in an Anthropic OAuth or canonical Codex pool does not cool the whole combo target.
  • Require a matching live account cooldown before skipping the target cooldown. Record Anthropic account health when a final 429 arrives after the request's retry limit; unknown-account 429s, other failures, API-key providers, and generic OAuth providers retain target cooling.
  • Add regression coverage, register the new test in both layout manifests, and update the combo contract and user guide.

Verification

  • bun install --frozen-lockfile — passed.
  • RT6 fix-up after merging current origin/dev: bun test tests/routing/always-on-429-failover.test.ts tests/server/server-combo-cooldown-recording.test.ts tests/adapters/anthropic/anthropic-combo-account-cooldown.test.ts — 18 passed, 0 failed. The source oracle now checks the quorum gate in recordAnthropicAccount429, which the rotator calls before choosing a replacement.
  • bun test tests/ci-workflows/file-size-ratchet.test.ts tests/test-layout.test.ts tests/test-layout-tooling.test.ts — 27 passed, 0 failed; bun run typecheck, bun run privacy:scan, and git diff --check — passed on the new head.
  • bun run typecheck — passed.
  • bun test tests/server/server-combo-cooldown-recording.test.ts tests/adapters/anthropic/anthropic-combo-account-cooldown.test.ts — 9 passed, 0 failed.
  • The lane's combined command (bun test tests/adapters/anthropic tests/oauth/oauth-accounts-api.test.ts tests/cli/cli-account-pool-verbs.test.ts tests/server/server-combo-cooldown-recording.test.ts tests/usage/usage-provider-label.test.ts) — 649 passed, 5 failed, all in anthropic-model-routes.test.ts. That file passed alone (15/0). Running the same 39 files one process at a time passed 654/0; the combined-run interaction remains to be resolved by CI or follow-up investigation.
  • bun test tests/test-layout.test.ts tests/test-layout-tooling.test.ts tests/ci-workflows/file-size-ratchet.test.ts tests/lab/core-lab-boundary.test.ts — 52 passed, 0 failed.
  • bun run structure:check, bun run skill:surface:check, bun run privacy:scan, git diff --check — passed.
  • cd docs-site && bun install --frozen-lockfile && bun run build — passed; 537 pages built.
  • Full bun run test was not run locally because four RT6 lanes share this machine. Exact-head CI run 36615011801 failed test 2/4: batch 11 timed out after 120 seconds in a multi-file process; all 12 files passed in the CI per-file attribution sweep. The PR remains draft. Full local tests were omitted because four RT6 lanes share this machine. Security review of the pool bookkeeping is pending.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults. Independent review is pending.

Co-authored-by: Vadym O bolein95@gmail.com

vadymhimself and others added 3 commits September 30, 2026 03:13
`advanceComboAfterFailure` cooled the combo target for any target-scoped
failure, and `cooldownMapKey` is `comboId + provider/model` with no account in
it. A provider with an account pool therefore shared one cooldown entry across
every account, so a 429 from a single spent account removed the whole target
from selection and the remaining accounts were never tried.

On 2026-09-28, 01:17-05:18 WITA, one Anthropic account answered
`Retry-After: 318747` (88.5 hours, its weekly window gone).
`anthropic/claude-opus-5-5` was cooled outright and the gateway returned 503
with zero upstream sends for four hours, while a direct probe showed four of
five accounts returning HTTP 200.

A 429 names the ACCOUNT that is spent, not the target. Every account pool
already records that account's own cooldown and routes past it
(`rotateAnthropicAccountOn429`, `rotateKeyOn429`, the Codex health store), so
the combo layer needs to stop duplicating that decision at target scope rather
than to grow a second cooldown map beside the pool's.

The account is read from the provider label the dispatch stamped
(`anthropic-p2e22d0`), which a pool rewrites and a configured provider name
never matches. `accountLogLabel` is deliberately not used: it is stamped for a
lone configured API key too, so it cannot tell a pooled account apart from a
provider with no pool to rotate within.

Unknown account still cools the target, which is the half that keeps this from
trading a blackhole for a hammer. It also lands exactly where it is needed:
when every account is cooled, the pool refuses locally with a 429 BEFORE it
picks one, so no account is named, the target cools on the pool's own
Retry-After, and nothing is sent upstream. Target-level failures (provider
down, model gone) and provider-scoped credential failures are untouched.

Co-Authored-By: Claude Code <noreply@anthropic.com>
(cherry picked from commit 62bfd5f)
CodeRabbit review on #6234.

`cooled` is a filtered subset of `ids`, so its positions are not account
indexes. Mapping it with `cooled.map((_, index) => credential(index))` built
tokens for accounts 0 and 1 when accounts 0 and 2 were the cooled pair, and the
assertion would then pass even if the retry left through cooled account 2 --
the exact failure the test exists to catch.

Each cooled id now resolves to its own token, and the same claim is asserted
from the other direction: the token actually sent is mapped back to its account
and that account must be one the first request never cooled.

Co-Authored-By: Claude Code <noreply@anthropic.com>
(cherry picked from commit e8ee8a8)
Record Anthropic 429 health after the per-request retry cap, and skip combo target cooling only when the attributed account has a live pool cooldown. Keep unknown and non-pool failures on the target path.

Co-authored-by: Vadym O <bolein95@gmail.com>
@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Repository: lidge-jun/opencodex/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 73a25958-fa7e-41e7-a7cf-0f07d1639994

📥 Commits

Reviewing files that changed from the base of the PR and between 6da09f6 and 2f77377.

📒 Files selected for processing (16)
  • docs-site/src/content/docs/guides/combos.md
  • scripts/test-layout/layout.json
  • src/combos/resolve.ts
  • src/oauth/anthropic-routing.ts
  • src/providers/label.ts
  • src/server/responses/adapter-continuation.ts
  • src/server/responses/adapter-dispatch.ts
  • src/server/responses/core-combo.ts
  • structure/providers-and-adapters.md
  • structure/runtime.md
  • structure/transports/inventory.md
  • tests/adapters/anthropic/anthropic-combo-account-cooldown.test.ts
  • tests/fixtures/test-layout-expected.json
  • tests/routing/always-on-429-failover.test.ts
  • tests/server/server-combo-cooldown-recording.test.ts
  • tests/usage/usage-provider-label.test.ts
 ________________________________________________________________
< Fully armed and operationally intelligent code reviewer bunny. >
 ----------------------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

✅ Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the bug Something isn't working label Sep 29, 2026
@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 70 / 80

이 PR은 #6234를 dev로 가져오면서, 콤보가 타깃을 안 식히는 조건을 한 단계 더 세게 잡는다. 예전에는 풀이 계정 이름을 라벨에 찍기만 하면 타깃 쿨다운을 건너뛰었다. 지금은 그 계정이 풀 health에 실제로 살아 있는 쿨다운으로 기록돼 있을 때만 건너뛴다. Anthropic은 요청 안 재시도 한도가 끝난 마지막 429에서도 recordAnthropicAccount429로 health를 남기고, Codex는 이미 있는 quota health를 본다. 계정 이름을 못 찾거나, health가 없거나, 풀이 “남은 계정 없음”으로 로컬 429를 내면 예전처럼 타깃을 식힌다. 그래서 빈 타깃을 두들기는 쪽으로 기울지 않는다.

현장 이야기는 #6234와 같다. 2026-09-28에 Anthropic 계정 하나가 Retry-After: 318747(약 88.5시간)을 줬고, 콤보가 anthropic/claude-opus-5-5 타깃 전체를 식혀 네 시간 동안 업스트림 전송 없이 503을 냈다. 그때 직접 찔러 보면 다섯 계정 중 넷은 200이었다. 풀은 이미 계정만 식히고 돌리는데, 콤보가 타깃 단위로 한 번 더 식혀서 남는 계정을 쓰지 못한 구멍이다.

베이스는 dev다. types.ts/config.ts 분할과 겹치는 파일은 없다. 같은 주제의 #6234는 아직 열려 있고, 이 PR이 그걸 carry한 뒤 live cooldown 조건을 더한 후속이다. draft 상태이고, 작성자도 보안 리뷰·전체 suite는 CI에 맡긴다고 적었다.

라인 - tests/routing/always-on-429-failover.test.ts / src/oauth/anthropic-routing.ts: exact-head CI test 4/4가 실패했다. proactive Anthropic routing stays opt-in > the rotator cannot be re-gated behind the pool flag가 rotateAnthropicAccountOn429 소스에 hasAnthropicFailoverQuorum이 있다고 기대하는데, 그 검사가 recordAnthropicAccount429로 옮겨졌다. 동작은 같고 소스 스캔만 깨졌다. 테스트가 recordAnthropicAccount429(또는 rotate가 record를 호출하는 사실)를 보도록 고치세요.

라인 - tests/usage/usage-provider-label.test.ts poolAccountProviderLabel("anthropic-kabc123", …): kabc123은 k + hex32 형식이 아니라서 실패한다. 형식만 맞는 k + 32hex면 ACCOUNT_LOG_LABEL_RE에 걸려 파서는 통과한다. 단독 API 키 라벨이 콤보 가드를 켜지 않는다는 증명은 이 줄이 아니다. 지금은 cooledPoolAccountLabel의 health 조회가 막아 주니, 그 단위 테스트나 형식에 맞는 k 케이스를 넣는 편이 낫다.

라인 - tests/server/server-combo-cooldown-recording.test.ts: failedAccount 문자열만 넣어 resolve 가드를 본다. live health가 없을 때 cooledPoolAccountLabel이 undefined를 돌려 타깃을 식히는지, Codex 경로가 같은지까지는 이 테스트가 안 본다. e2e Anthropic 테스트만으로 Codex·health-없음 쪽은 비어 있다.

메인테이너의 판단이 필요한 지점

#6234와 #6255 중 무엇을 남길지. 이 PR이 carry + 강화분이면 #6234는 닫는 쪽이 맞다. Codex는 마지막 429 health 기록이 Anthropic만큼 명시적으로 추가되지 않았다. Codex 콤보도 같은 이득을 줘야 하면 기록 지점을 맞출지, 지금은 Anthropic 사고만 고치고 Codex는 health가 있을 때만 이득을 보게 둘지. CI 빨간 소스 스캔을 이 PR에서 고칠지.

너의 추천

방향은 맞다. 풀이 이미 계정 쿨다운을 갖고 있으니 콤보가 타깃을 한 번 더 식히지 않는 쪽이 맞고, live health를 요구한 것도 #6234보다 안전하다. 머지 전에 always-on-429 소스 스캔 실패부터 고치고, #6234는 이 PR에 흡수되면 닫으세요. draft 해제·체크리스트·보안 리뷰는 그다음이다.

이 댓글은 grok-bot이 작성했습니다

@lidge-jun
lidge-jun marked this pull request as ready for review September 29, 2026 19:32
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner September 29, 2026 19:32
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-29T19:35:21.098347Z 2f77377 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@lidge-jun
lidge-jun merged commit 99878c3 into dev Sep 29, 2026
64 of 67 checks passed
@lidge-jun
lidge-jun deleted the codex/rt6-l2-combo-account-cooldown branch September 29, 2026 19:33

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2f7737756b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

config,
pick.target.provider,
pick.target.model,
poolAccountProviderLabel(childLog.provider, pick.target.provider),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Resolve the main Codex pool account from its account label

When a canonical Codex pool dispatch uses the main-pool account and its terminal 429 records a cooldown, formatCodexProviderForLog deliberately leaves childLog.provider equal to the configured provider while childLog.accountLogLabel is main (src/codex/routing.ts:1617-1625, src/server/responses/request-prepare.ts:1307-1310). Passing only the provider label here therefore yields no failedAccount, and the combo cools the entire target even when a stored backup account is healthy. Pass the account label as well and resolve MAIN_CODEX_ACCOUNT_ID when it has matching live quota health.

Useful? React with 👍 / 👎.

if (providerName === "anthropic") {
const matches = getAccountSet("anthropic")?.accounts.filter(account =>
formatAnthropicProviderForLog("anthropic", account.id) === label) ?? [];
return matches.length === 1 && getAnthropicAccountHealthSnapshot(matches[0]!.id) ? label : undefined;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Record terminal sidecar 429s before requiring account health

For an Anthropic combo using a web-search or image sidecar, sidecar-execution.ts:262-306 returns a terminal 429 without calling recordAnthropicAccount429 when the shared hop reservation is denied or the three-failover cap has already been reached. The current account consequently has no health snapshot here, so a five-account pool that exhausts the fourth account cools the whole combo target and blocks the untouched fifth account. Apply the same terminal-429 recorder added to the dispatch and continuation loops before these sidecar returns.

Useful? React with 👍 / 👎.

vadymhimself added a commit to vadymhimself/opencodex that referenced this pull request Oct 1, 2026
…pool

When every Anthropic OAuth account is cooled, the pool refuses locally with a 429
whose Retry-After is `getAnthropicPoolRetryAfterSeconds`: the earliest per-account
cooldown, restated. lidge-jun#6255 deliberately keeps cooling the combo TARGET on that
refusal, since no account was identified, so the server-delay path records it as
an authoritative upstream delay of up to the 24h ceiling.

That number is local state, not an upstream statement, and local state changes:
an account is added, resumed, re-authenticated or has its cooldown cleared. Only a
combo edit or delete clears a target cooldown (`clearComboTargetCooldowns` is
called solely from management/combo-routes.ts), so after a weekly window
exhausts every account, a freshly added account sits ignored by every combo that
routes through Anthropic until the target expires, up to a day, while the pool
itself would already serve it.

`isAnthropicPoolLocalRefusal()` classifies that one response by provenance, not
by current pool state alone: the target must be the OAuth `anthropic` provider,
no account may have been identified for the failure, the status must be 429, and
the pool must be holding the wait (no eligible account, at least one known
cooldown expiry). Only then is the pool's restated Retry-After withheld from the
target cooldown. The target still cools (the anti-hammer guard lidge-jun#6255 intended),
but for the local fallback, which `coolComboTarget` already caps at ten minutes.
Re-offering the target while the pool still holds the wait costs a local refusal
with zero upstream sends.

A genuine upstream Retry-After keeps parking the target in full: an upstream 429
on a pool account always cools and names that account, an API-key provider never
qualifies, and a single account is never cooled by rotation. The tested
wait-vs-failover contract in combo-authoritative-reset.test.ts passes unchanged.

The classifier lives in core-combo.ts and only reads the pool through existing
exports, so no auth surface is modified.

Verification: `bun run typecheck` clean. always-on-429-failover: 11/11 with two
new cases covering the pool-held refusal, an identified account, an API-key
provider, a non-429 status, recovery on a newly saved account, and the target
cooldown expiring at ten minutes rather than the pool's four-day Retry-After.
Both fail with the classifier forced false. tests/routing plus the three combo
cooldown suites: 1249 pass / 21 fail against 1247 / 21 on dev, the same 21
pre-existing failures.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
vadymhimself added a commit to vadymhimself/opencodex that referenced this pull request Oct 1, 2026
…pool

When every Anthropic OAuth account is cooled, the pool refuses locally with a 429
whose Retry-After is `getAnthropicPoolRetryAfterSeconds`: the earliest per-account
cooldown, restated. lidge-jun#6255 deliberately keeps cooling the combo TARGET on that
refusal, since no account was identified, so the server-delay path records it as
an authoritative upstream delay of up to the 24h ceiling.

That number is local state, not an upstream statement, and local state changes:
an account is added, resumed, re-authenticated or has its cooldown cleared. Only a
combo edit or delete clears a target cooldown (`clearComboTargetCooldowns` is
called solely from management/combo-routes.ts), so after a weekly window
exhausts every account, a freshly added account sits ignored by every combo that
routes through Anthropic until the target expires, up to a day, while the pool
itself would already serve it.

`isAnthropicPoolLocalRefusal()` classifies that one response by provenance, not
by current pool state alone: the target must be the OAuth `anthropic` provider,
no account may have been identified for the failure, the status must be 429, and
the pool must be holding the wait (no eligible account, at least one known
cooldown expiry). Only then is the pool's restated Retry-After withheld from the
target cooldown. The target still cools (the anti-hammer guard lidge-jun#6255 intended),
but for the local fallback, which `coolComboTarget` already caps at ten minutes.
Re-offering the target while the pool still holds the wait costs a local refusal
with zero upstream sends.

A genuine upstream Retry-After keeps parking the target in full: an upstream 429
on a pool account always cools and names that account, an API-key provider never
qualifies, and a single account is never cooled by rotation. The tested
wait-vs-failover contract in combo-authoritative-reset.test.ts passes unchanged.

The classifier lives in core-combo.ts and only reads the pool through existing
exports, so no auth surface is modified.

Verification: `bun run typecheck` clean. always-on-429-failover: 12/12 with three
new cases. One drives the real `executeComboResponses` failure path with a child
that answers the pool's 429 and a four-day Retry-After: the target re-opens at ten
minutes when the pool holds the wait, and stays parked in the control run where
the same 429 arrives with an account still eligible; reverting only the call site
to always forward Retry-After fails it. The other two cover the pool-held refusal, an identified account, an API-key
provider, a non-429 status, recovery on a newly saved account, and the target
cooldown expiring at ten minutes rather than the pool's four-day Retry-After.
Both fail with the classifier forced false. tests/routing plus the three combo
cooldown suites: 1249 pass / 21 fail against 1247 / 21 on dev, the same 21
pre-existing failures.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
lidge-jun added a commit that referenced this pull request Oct 1, 2026
…pool (#6383)

When every Anthropic OAuth account is cooled, the pool refuses locally with a 429 whose Retry-After restates the earliest per-account cooldown. The combo target now cools for the local fallback on that pool-local refusal instead of the restated wait (up to the 24h server-delay cap), so a resumed or newly added account is not ignored. Genuine upstream 429s (API-key providers, identified pool accounts) keep their full Retry-After.

Follow-up to #6255.

Co-authored-by: lidge-jun <jun@lidgeai.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants