Skip to content

fix(responses): bound API-key 429 rotations across continuations - #4621

Draft
luvs01 wants to merge 3 commits into
lidge-jun:devfrom
luvs01:agent/key429-rotation-cap-20260914
Draft

fix(responses): bound API-key 429 rotations across continuations#4621
luvs01 wants to merge 3 commits into
lidge-jun:devfrom
luvs01:agent/key429-rotation-cap-20260914

Conversation

@luvs01

@luvs01 luvs01 commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Summary

An API-key provider with a short 429 cooldown can select an already-tried key again after its cooldown expires. Default reset-only transports do not enable the optional shared transient retry budget, so repeated 429s can keep the request rotating.

Bound successful key-pool failovers within each generic response invocation to the initial pool size minus one when the initially selected key reference belongs to the pool, or the full pool size when it is outside the pool. Initial recovery, web-search/image/video bridge rounds and terminal continuations share that counter. Initialization precedes sidecar dispatch. The sidecar callback applies the API-key count while preserving its existing reset/same-target policies and OAuth/Anthropic account fallback; it does not gain the ordinary core physical-send budget. Existing physical-send budgets still stop dispatch earlier when applicable. On exhaustion, record the final proven failed key's cooldown without selecting or persisting another key, and preserve the final upstream failure. A superseded key-selection snapshot cannot alter key health.

This is an invocation-level bound, not a combo-wide bound or a guarantee that each distinct key is tried only once. Source-owner documentation and all eight provider-configuration translations describe that scope.

Verification

  • Based on upstream dev 62f02223a0d1b2f71d8bc8a07d29703ad77a7ca9; Bun 1.4.2 on Windows.

  • The bounded pre-fix regression failed in all three original scenarios: default 429 recovery, recovery followed by a terminal continuation, and a transient-retry route. The fixture explicitly stops after six sends, preventing an unbounded test.

  • Current-source focused verification: 186 passing cases / 669 assertions in the key-failover, transient-retry, web-search and image-loop files. The combined invocation also reported one module-load error when the image-loop test mock omitted imageFulfillmentTailSnapshot needed by the server suite. Running that server file in a separate process passed all 22 cases / 149 assertions; the separate Anthropic sidecar fallback regression passed 1 case / 8 assertions. The failed combined invocation is retained as such, not relabeled green. Existing module mock replacement and the actual production export establish the isolation issue.

  • Typecheck, structure ownership check, privacy scan and diff check passed. The updated documentation build generated all 441 pages.

  • bun scripts/test.ts --changed=62f02223a0d1b2f71d8bc8a07d29703ad77a7ca9 reached the runner's 900-second limit (exit 124). Collected output contains 6,589 passing and 57 failing cases across the broad import graph; the run did not complete. Failures include timeouts and assertions in management, compact, routing, CLI and response paths. They have not all been attributed to the baseline or this change, so this is not a successful impact-suite result. The later current-head hosted run and automated reviews have now been assessed below; this historical incomplete local run is not relabeled as successful.

  • Current-head CI 34839412597 completed on 9336e38f99b2a65a062113d5ccba4eb559974956 with a failure conclusion. All other jobs passed; Windows 4/6 and 6/6 contain the exceptions below and therefore the aggregate gate also failed. This is author-ready with documented validation exceptions, not a claim that hosted CI is green.

  • Review regressions: an initially unpooled key failed before its correction (two sends instead of three), then passed with outside, A, B and final cooldown/error preservation. Actual web-search and image bridge requests then exposed a second missing rotation site: each alternated A/B until the six-send fixture ceiling on the preceding head. Both now stop after A/B, with bridge activation verified by the emitted synthetic tool and the final provider 429/cooldown preserved (2 cases, 14 assertions). The shared callback also serves the video bridge. Both identified findings have been addressed. Codex and CodeRabbit completed review of the current head without additional findings; all review threads are resolved. Current-head CI has been assessed with the documented exceptions.

  • Windows 4/6: nine unchanged desktop restart failures match the known Windows/POSIX fixture issue addressed separately in fix(codex): repair desktop restart membership and POSIX-only cases on Windows #4564. One additional native-profile-crash-boundaries case timed out waiting for its startup port file before reaching its teardown assertion. On this exact head, that case passed independently on Windows (1 test, 3 assertions, 5.59 seconds); the helper publishes its port before any Responses dispatch, outside the changed rotation path. The CI-only startup delay remains unattributed.

  • Windows 6/6: the existing paginated catalog-path assertion compares escaped TOML text to a raw Windows path (test(codex): compare the injected catalog path as a decoded TOML value #4568), and an unchanged auth-context fixture cleanup reports EPERM, matching an earlier observed failure. The specific cleanup handle/locking cause remains unknown. Neither failure exercises the changed rotation logic. No passing test was rerun merely to relabel this CI result.

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.

Review readiness checklist

Author readiness includes the explicit validation exceptions above; the hosted CI conclusion remains failure.

  • All CI tests are green on my local testing.
  • I pushed my PR to the latest dev commit.
  • I resolved all correct Codex and CodeRabbit findings.
  • My PR is ready for review.

Summary by CodeRabbit

  • New Features

    • Responses requests using multi-key pools now enforce a shared, request-scoped API-key rotation limit, including continuation requests.
    • Limits account for whether the initial key is in the pool and are not replenished by cooldown expiry or pool growth.
    • Failed rotations record cooldown information without selecting a replacement key.
  • Bug Fixes

    • Improved handling of exhausted rotation and retry budgets while preserving existing error responses.
  • Documentation

    • Updated provider, transport, and architecture documentation on bounded rotation and retry behavior.

@coderabbitai

coderabbitai Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 68a143d1-fb24-49db-a115-0f7d2d7d51d5

📥 Commits

Reviewing files that changed from the base of the PR and between e7dc2d7 and 9336e38.

📒 Files selected for processing (11)
  • docs-site/src/content/docs/fr/reference/configuration/providers.md
  • docs-site/src/content/docs/ja/reference/configuration/providers.md
  • docs-site/src/content/docs/ko/reference/configuration/providers.md
  • docs-site/src/content/docs/reference/configuration/providers.md
  • docs-site/src/content/docs/ru/reference/configuration/providers.md
  • docs-site/src/content/docs/tr/reference/configuration/providers.md
  • docs-site/src/content/docs/zh-cn/reference/configuration/providers.md
  • docs-site/src/content/docs/zh-tw/reference/configuration/providers.md
  • src/server/responses/core.ts
  • structure/transports/responses.md
  • tests/server/server-key-failover-e2e.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

The change bounds API-key 429 failover for generic Responses requests. Initial recovery, sidecar operations, and continuations share a fixed pool-based rotation limit. Failover preserves cooldown recording when no replacement is selected. Tests and documentation cover the contract.

Changes

Responses key failover

Layer / File(s) Summary
Failover rotation control
src/providers/key-failover.ts, tests/adapters/key-failover.test.ts
Failover accepts allowRotation. Disabled rotation records the failed key cooldown without selecting or persisting a replacement. Tests verify configuration bytes, ownership, events, and cooldown behavior.
Responses rotation budget
src/server/responses/core.ts, structure/transports/responses.md
The Responses adapter shares a fixed pool-based key-rotation budget across sidecar operations, initial recovery, and continuations. Existing send budgets can stop retries earlier. Continuation retries cannot re-arm an exhausted send budget.
Failover behavior validation
tests/server/server-key-failover-e2e.test.ts
End-to-end cases cover bridge, exhausted, continuation, transient, budget-exhausted, and unpooled 429 flows. Tests verify authorization keys, cooldown state, response errors, continuation behavior, and the outbound-request limit.
Contract documentation
docs-site/src/content/docs/*/reference/configuration/providers.md, structure/**/*.md
Localized provider documentation and structure references describe bounded rotation, cooldown handling, continuation sharing, and the scope of generic Responses failover.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ResponsesAdapter
  participant Provider
  participant KeyFailover
  Client->>ResponsesAdapter: submit Responses request
  ResponsesAdapter->>Provider: send request with current API key
  Provider-->>ResponsesAdapter: return 429
  ResponsesAdapter->>KeyFailover: request bounded rotation
  KeyFailover-->>ResponsesAdapter: record cooldown and return replacement or no replacement
  ResponsesAdapter->>Provider: send recovery or continuation request
  Provider-->>ResponsesAdapter: return response or final error
  ResponsesAdapter-->>Client: return Responses result
Loading

Suggested reviewers: lidge-j

Merge Risk: ⚪ Minimal · up to 9336e

The documented behavior matches the affected Responses request path, with no remaining actionable merge risk identified.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 4 files. (9 skipped: 9 u…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: bounding API-key 429 rotations across response continuations.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 14, 2026
@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

✅ READY

  • all PR quality gates passed; the review readiness checklist is complete.

Review readiness checklist

  • ✅ All CI tests are green on my local testing.
  • ✅ I pushed my PR to the latest dev commit.
  • ✅ I resolved all correct Codex and CodeRabbit findings.
  • ✅ My PR is ready for review.

4/4 boxes ticked.

Automatic ready-for-review conversion failed; please mark the pull request ready manually if it is still a draft.
The review-ready label marks this PR as ready; review automation runs independently.
Maintainers notified: @lidge-jun @Ingwannu

@luvs01

luvs01 commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@luvs01

luvs01 commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 14, 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-14T11:47:27.230611Z 9336e38 Manual request
ℹ️ 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

Copy link
Copy Markdown
Owner

리뷰 · 우선순위 64 / 80

설명

이 PR(작성자 luvs01, draft)은 API 키 풀에서 429가 났을 때 키가 무한히 돌 수 있는 구멍을 막는다. 짧은 쿨다운이 끝나면 이미 시도한 키를 다시 고를 수 있고, reset-only 전송은 공유 transient retry budget을 안 켜서 요청이 키만 바꾸며 계속 도는 장면이 나온다. 고치는 방법은 단순하다. handleResponsesInner 한 번 호출마다 첫 전송 전 풀 크기 N을 찍고, 최초 복구와 terminal continuation이 합쳐서 최대 N-1번만 키를 바꾸게 한다. 쿨다운이 풀리거나 나중에 풀이 커져도 이 숫자는 다시 채워지지 않는다.

현재 dev tip은 4f788f916 (#4620 출시 기록까지 포함)이고, 이 PR 본문은 기준을 62f02223a0이라고 적었다. 그 사이 #4618(2.56.0 open)과 #4620(docs)이 올라왔으니 리베이스가 필요하다. 코드 본체는 src/server/responses/core.tsmaxKeyPoolFailovers/keyPool429RetryAllowed 를 두고, src/providers/key-failover.tsrotateKeyOn429 / rotateProviderTransportOn429allowRotation 플래그를 추가한다. 거절 시에도 실패한 키 쿨다운은 기록하되 다른 키를 고르거나 디스크에 쓰지 않는다. 401 경로에는 이 플래그를 일부러 안 붙였다.

테스트는 핵심을 잘 잡는다. tests/adapters/key-failover.test.ts 의 health-only 시나리오는 allowRotation=false일 때 설정 파일 바이트·선택 이벤트·쿨다운을 검사하고, tests/server/server-key-failover-e2e.test.ts 는 exhausted/continuation/transient/budget-exhausted 네 모드로 전송 횟수·최종 본문·취소 여부를 고정한다. 픽스처가 6회를 넘기면 바로 터지게 해서 '테스트가 타임아웃으로만 실패하던' 예전 형태를 피했다. 작성자가 밝힌 대로 좁은 스위트 74통과는 있지만, --changed 임팩트 스위트는 900초 제한에 걸렸고 6589 pass / 57 fail로 끝나지 않았다. draft 유지 이유가 그것이다.

문서 쪽은 영어 providers.md와 8개 로케일, 그리고 structure/transports/responses.mdBounded API-key 429 rotation 절이 계약의 본체다. 그런데 같은 한 문장 링크를 adapters/catalog/clients/data-planes/gui/ops/xai/runtime/subagents/byte-accounting/inventory/streaming-health 등 관련 없는 structure 문서 여러 장 맨 위에 복붙했다. ownership 문서 폭발은 이 레포에서 자주 리뷰 지적되는 패턴이다. cost-guard #4546의 send-budget 계열(#4605~#4616)과 맞물리는 '요청 단위 상한'이라 방향은 현재 dev 과 잘 맞고, types/config 분할에 무효화되지도 않는다.

우선순위 64는 '실제 비용/루프 버그를 막는 코드'라서 문서 PR보다 높고, 동시에 draft·임팩트 스위트 미완료·structure 산포·tip 뒤처짐 때문에 70대로 올리지 않은 점수다. 콤보 전체 예산이 아니고 '키를 한 번씩만' 보장도 아니라고 본문이 솔직히 말한다. 그 범위를 메인테이너가 받아들일지가 머지 판단의 핵심이다.

src/server/responses/core.ts keyPool429RetryAllowed - continuation=false일 때만 auth-recovery reserve를 엿보는데, countedExternally:true 로 reserveDispatch를 호출한다. '검사만 하고 소비하지 않는다'는 주석과 실제 reserve API 의미가 같은지 tip 기준으로 한 번 더 확인이 필요하다.
src/server/responses/core.ts maxKeyPoolFailovers - apiKeyPool이 없거나 길이 1이면 상한이 0이라 회전이 아예 막힌다. 의도가 '풀이 없으면 돌리지 말 것'이면 맞고, 예전처럼 단일 키 경로의 다른 복구는 그대로인지도 회귀로 박아 두면 좋다.
structure/* 다수 파일 - 무관한 ownership 문서 상단에 동일 문장 링크를 뿌린 것은 계약 본체인 responses.md만 두고 나머지로의 back-link는 최소화하는 편이 이 레포 습관에 맞다.
PR base 62f02223a0 vs 현재 tip 4f788f916 - #4618/#4620 이후로 rebase 필요. 충돌 가능성은 docs/structure 쪽보다 core.ts send-budget 인접이 더 민감하다.
Verification - 로컬 --changed 57 fail 미귀속 상태로 draft. Ready 체크리스트도 CI/Codex·CodeRabbit 미해결로 비어 있다.

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

너의 추천
draft 유지. dev 4f788f916 위로 rebase한 뒤, structure 산포 문장을 걷거나 responses.md+providers 로케일만 남기고, 좁은 키-failover/e2e 스위트와 hosted CI가 초록인지 확인한 다음에만 Ready로 올린다. 지금 상태로는 머지하지 말고, #4546 레인에 'key-429 rotation cap'으로만 연결해 둔다.

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

@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: 78957f72f0

ℹ️ 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".

Comment thread src/server/responses/core.ts Outdated
@luvs01

luvs01 commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@luvs01

luvs01 commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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: e7dc2d7343

ℹ️ 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".

Comment thread src/server/responses/core.ts Outdated
@luvs01

luvs01 commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@luvs01

luvs01 commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

Reviewed commit: 9336e38f99

ℹ️ 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".

@luvs01

luvs01 commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

Updated author evidence for the current head 9336e38. Both automated reviews are complete and their two findings are resolved, including the initially unpooled key and sidecar sites. CI completed with the explicitly documented Windows exceptions; this is author-ready, not a green-CI claim.

The reserve question was rechecked: reserveDispatch returns a permit, and shared state is charged only by permit.use(); the allowance query does not call it. Pools with fewer than two entries already decline key rotation in rotateKeyAfterFailure, while existing same-target retry and OAuth fallback policies remain separate.

The referenced dev changes from 62f0222 to 4f788f9 are a version bump and release evidence, with no diff in core.ts, key-failover.ts or request-execution-budget.ts. Rebasing solely to move the base label would restart validation without changing those semantics. The ownership manifest was not expanded: the short links satisfy its existing source-owner mapping while the contract stays in one canonical section. Maintainer acceptance of the invocation-level design remains a merge decision.

The four author-checklist boxes and review-ready label are complete. GitHub denied the author account permission to mark this PR ready, and the repository automation also reported a failed draft conversion. It therefore remains technically draft; a maintainer must perform that state transition. I have not retried the denied mutation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working review-ready

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants