[WRONG BRANCH] chore(release): promote the verified 2.55.0 product tree to main - #4619
Conversation
merge dev into preview for the v2.33.0-preview.20260825 release (api-usage oracle fix)
[WRONG BRANCH] release: promote dev to preview for v2.34.0-preview.20260827
# Conflicts: # package.json
[WRONG BRANCH] promote dev onto preview for v2.35.0-preview
[WRONG BRANCH] release: v2.36.0-preview.20260829
`preview` carries its own release-bump commits (`release: v2.36.0-preview.20260829`), which are not on `dev`, so the promotion cannot fast-forward: `package.json` is the only conflicting path. Resolved to the version this train publishes, `2.36.0-preview.20260830`, which moves the `preview` channel forward from the 20260829 tip. Every other path takes `dev` (`bb6a6fbdf`) unchanged.
[WRONG BRANCH] promote dev onto preview for v2.36.0-preview.20260830
# Conflicts: # package.json
…ks up the ReDoS fix)
[WRONG BRANCH] promote dev onto preview for v2.38.0-preview.20260831
…23900901 # Conflicts: # package.json
[WRONG BRANCH] promote dev onto preview for v2.39.0-preview.20260901
release: promote dev to preview for v2.40.0-preview.20260902
…rkflow call (#3262) Both v2.40.0 release dispatches (33615174183 preview, 33615177849 main) died at startup_failure: a workflow_call cannot grant its callee more than the calling job holds, and dev-version-bump.yml's job declares contents+pull- requests write. #3129 wired the call but never dispatched a release, so this is its first live run. The caller job now declares exactly the callee's two permissions; no other job in release.yml gains anything. Co-authored-by: jun <jun@lidge.dev> (cherry picked from commit 7ce0ba5)
release: carry the release.yml permissions fix onto preview for v2.40.0-preview.20260902
Brings the reviewed dev head (81a1fc1) onto preview. The only conflict is package.json's version, resolved to the preview channel's next version rather than to either side: preview carried 2.40.0-preview.20260902 and dev carries 2.41.0, and neither is what this publish is. The 37-commit delta since v2.40.0 is reviewed per-commit in devlog/_plan/260903_muse_release_train/011_review_ledger.md.
[WRONG BRANCH] release: v2.41.0-preview.20260903
[WRONG BRANCH] release: refresh preview onto the audit-clean dev head for v2.41.0-preview.20260903
v2.41.0 published from main while this promotion was in flight, so the preview line has to move past it: a 2.41.0-preview version is now BEHIND a released one, which tests/release-version-line.test.ts refuses and npm would refuse after it. dev moved to 2.42.0 in #3354, the automatic post-release bump. Preview follows the same line at 2.42.0-preview.20260903, which is the conflict resolution here: neither side's value, because neither is what this publish is. Also brings the three CI timing-flake fixes from #3351, which is what preview CI failed on twice at 491ffcf. Preflight: 2.42.0-preview.20260903 unpublished on npm, no v2.42.0* tag, no GitHub release, and it moves preview forward from 2.40.0-preview.20260902.
[WRONG BRANCH] release: v2.42.0-preview.20260903
…dges [WRONG BRANCH] release: promote dev onto preview for the main-account badge fix
…view preview had diverged from main: it carried 28 merge commits main did not have, while main carried 23 preview did not, so preview could not fast-forward. The divergence was history only - the sole CONTENT difference between the two branches was the package.json version line. This merge takes main (48f8186, the v2.42.0 release commit) into preview and resolves the tree to main's content, so preview now matches the released tree exactly apart from its own prerelease version line. The version could not stay at 2.42.0-preview.20260903: v2.42.0 has published, and compareReleaseTags ranks that prerelease BEHIND its own stable release (-1), which is what tests/release-version-line.test.ts fails on. preview opens the next prerelease line at 2.43.0-preview.20260904, matching dev's 2.43.0.
[WRONG BRANCH] chore(release): promote main v2.42.0 onto preview and open 2.43.0-preview
…d agree with preview (#4546) (#4589) Follow-up to #4580, from review of the merged commit. P1: preview and resolve disagreed on the FIRST detour. Preview refused to pick one because pickRoundRobinAccount commits and advances the ring, so it returned null and fell through to the ordinary binding while resolve served from a fresh alternate. Subagent fallback scores the previewed account to decide whether a model is reachable, so it could retire a model over usage the request would never touch. Preview now peeks the same candidate through peekAlternateCodexAccount, which delegates for every strategy except round-robin because that is the only branch with a side effect. P1: when no detour existed the code fell through and deleted the binding. A provider-wide 503 soft-avoids every sibling, which is precisely when the candidate list is empty, so the hold did not cover the failure it was written for. Being unable to send is not the same as forgetting which account owns the conversation: the binding now survives and the bound account is returned, on both the ordinary and model-detour lanes. reset-first could still move a bound thread onto an account with no usage reading, because hasCodexQuotaHeadroom answers true for unknown. The quota strategy excludes those through its strictly-cooler compare; reset ordering has no such compare and now says it explicitly.
* docs(devlog): record the round 2 outcome and close the delivery unit Three wave-A lanes landed with one config-schema owner and no collisions. Unit totals: 22 pull requests closed, 13 issues closed, three issues deliberately left open with their landed scope recorded, three follow-ups filed. * docs(devlog): move the round 2/3 delivery unit to _fin Both rounds delivered and the closure sweep verified against live GitHub state: 22 pull requests terminal, 13 issues closed, three issues deliberately left open with their landed scope recorded.
… turn (#4595) * docs(devlog): plan the regression audit and the 2.55.0 release Names the six source files three separate merges each touched in the dev delta, states that the audit reads the merged state rather than any single diff, and records the one deliberate deviation from the release script preflight along with what covers each skipped check. * fix(web-search): do not release a withheld client call under a failed turn A mixed bridge leg whose upstream terminal was response.failed released its withheld client-executed tool call, because the mixed-tool termination change routed both failed and incomplete terminals through the same endWithoutSearch branch and that branch flushes held calls. The failure path ten lines above documents the opposite rule: releasing a tool call the client would start executing is exactly what must not happen under a turn that is already over. The two terminals differ. An incomplete turn is one the client can still act on, so its held call goes back; a failed turn is over. The decision now carries whether held calls may be released, true only for incomplete, and the emit path drops them otherwise. The hosted cell still closes in both cases, which is what the reordering was for. Found by a cross-merge regression audit of the dev delta before promotion. Records the audit findings for all five contended file groups alongside the fix. * docs(devlog): correct the release plan version-line and CI-event facts preview and main carry two different version lines over the same product tree, because release.yml requires package.json to equal the dispatched version and a preview dispatch must be a prerelease. Only the clean-tree guard and the npm channel-forward check are script-only. And only a push-event CI run on the release branch satisfies the publish gate; a green PR run at the same SHA is refused. * docs(devlog): write the 2.55.0 release runbook The exact ordered sequence with the gate gating each step, including the two version lines over one product tree, the push-event CI requirement, the Service lifecycle prerequisite, and the dev move that must precede the stable publish.
…4546) (#4592) * feat(codex): record why a live binding was kept, moved, or released (#4546) logCtx.affinity was typed and persisted but never assigned, and routing had no reason to report, so an account move was only visible by comparing account labels across log lines. resolveCodexAccountForThreadDetailed now returns the decision and its cause, the pool auth context carries it, and the usage entry persists both move and reason. * fix(codex): hoist the affinity decision to the pool context scope The declaration sat inside the selection block and the spread landed on the main-pool return, so the pool context never carried it and typecheck failed. Reading resolution.affinity through an in-check keeps the fixed-account branch of the union valid. * fix(codex): report the affinity decision on every selection path A first placement returned through the active-account retention path, which carried no decision, so the record was missing for exactly the case that establishes a binding. All selection returns now report, and the detailed-resolver assertions move to toMatchObject because the resolution carries a field they did not previously expect. * fix(codex): carry a release reason from the outcome path to the next resolve A 429 clears the pin inside recordCodexUpstreamOutcome, so the request that pays for the cold prefix arrived with nothing left to explain why. The reason is now held per thread, bounded, and consumed by that thread next resolve. Two routing cases compared whole resolutions to each other and now compare the account, because a first placement and a later reuse legitimately report different decisions. * fix(test): compare the account, not the whole resolution, for model detour independence * fix(test): compare the account for the second model detour lane too * fix(test): tolerate the affinity decision in the 401 replay resolution check
…d-budget owner (#4602) * docs(devlog): plan wp4 send budget at diff level * docs(devlog): correct the wp4 send-budget plan from the audit round Four claims were wrong: the #2981 helper is not the opt-in part and Codex passthrough gets a fresh allowance per leg; the same-request account resend is retryCodexPoolOnAlternateAccount, not applyFailureFailover; continuation repair is already covered on the policy path while empty-completion, rebuildAndRefetch, compact and generic OAuth hops are not; and Retry-After is already shortened by local caps, so treating it as a lower bound is a behavior change. A 3-send ceiling would also break the 3+1 recovery the plan measured. * docs(devlog): locate the send-budget owner and why the passthrough escapes it handleResponses already owns a request-scoped transient budget and documents itself as covering recovery refetches, but the Codex passthrough legs sit in an earlier scope and pass neither attempts nor onSendsConsumed, so each takes a fresh default of 3. That is the source of the measured 4/7/12, and hoisting the owner is the smallest first step.
…oss a no-account resolve (#4598) (#4604) The release reason re-derived a subset of the selectable guards and fell through to a quota fallback, so paused, plan-excluded, cooled-down and quota-avoided releases named a cause routing never used. It now comes from the same predicates in the same order as isCodexAccountSelectable. Separately, a no-account return carried no payload and the pending reason was consumed before selection, so a release that failed to find a replacement was never recorded; the reason is now reported on that return, handed forward, and forgotten only once reported.
…rough (#4546) (#4605) * fix(responses): share one transient send budget with the Codex passthrough (#4546) The budget owner was declared below the passthrough branch, so it was in the temporal dead zone for those four sends and each took the helper fresh default of 3. Hoisting it above the branch and wiring the sends makes one logical request share one transient budget across its recovery legs. The cross-account alternate is untouched because it does not go through the helper, so the 3+1 recovery shape is preserved. * test(responses): pin the shared transient budget across a sanitized rebuild The repeated function-output decrypt case sent 6 times (3 on the first leg, a fresh 3 on the rebuild). With the budget shared it sends 4: the rebuild draws on what is left rather than a new allowance. That count is the regression for #4546. * test(lib): pin the passthrough legs into the shared-budget source oracle The oracle asserted exactly three legs report into the counter. The four Codex passthrough sends now do too, and the oracle names them plus the transientRetryPolicyFor gate that would silently restore a fresh allowance.
) (#4606) * feat(logs): surface the account decision in the route explanation (#4546) The affinity move and its reason were persisted but never exposed, so the record only existed for someone willing to parse usage.jsonl. The route-decision endpoint behind ocx logs explain now carries them, null for rows that have no account decision. * fix(usage): persist the affinity record the writer was already setting appendUsageEntry builds the persisted entry from an explicit whitelist, so affinity and affinityReason were dropped on write and #4592 never reached disk. Both are now normalized against known value sets, and a reason is kept only alongside a move.
…#4546) (#4608) The budget was a counter local to one handleResponsesInner frame, and a combo parent runs a separate child turn per target, so a three-target fan-out took three fresh allowances. It is now a holder on HandleResponsesOptions, minted at genuine ingress and inherited by children through the options spread that already carries comboAttempt and translatorBudget.
…ero (#4546) (#4609) * fix(responses): one send budget per logical request, and zero means zero (#4546) Refs #4546. wp4 steps 2-4 of the cost-guard roadmap. The amplification behind #4546 was never one missing limit. Every layer that can re-send counted its own allowance, so a per-layer 3 composed into a per-request 12. #4605 and #4608 gave the transient layers one shared counter; this gives that counter a policy. src/lib/request-execution-budget.ts carries the guarded text-Codex profile: four model sends per logical request, a base allowance of three shared by the initial send and same-target retries, and ONE final-recovery reserve that an account move and a validated rebuild share rather than taking one each. The permit is consumed immediately before the physical send, not reconciled after the helper returns, because a counter read afterwards cannot stop two legs that both saw the same remainder. Zero now means zero. The Math.max(1, ...) floors in remainingTransientSendBudget and in both retry helpers funded one more send on every recovery leg, which is most of how a bounded per-leg allowance became an unbounded per-request count. A refused send raises the typed SendBudgetExhaustedError, which UpstreamRetryEvidenceError no longer wraps and which transportFailureResponse maps to request_send_budget_exhausted instead of reporting a proxy decision as a 502 upstream fault. Where a reusable upstream answer already exists, the refusal happens before that body is cancelled: the native OAuth 401 replay and the same-target 429 wait now check the remainder in their own conditions, so an exhausted request returns the real 401 or 429 with its Retry-After rather than a synthetic 502. Two holes that survived #4608 are closed. The adapter initial send passed the raw policy on the argument that nothing had been spent yet, which is false for a combo child: it inherited the parent's holder and then took a fresh full allowance anyway. And the cross-account move was bounded by nothing per request -- excludeAccountId excludes only the account that just failed, and the recovery loop can return after the alternate fails too, so one request could walk the pool an account at a time. Deliberately out of scope, recorded rather than hidden: the same-account gated-model 400 ladder keeps its own maxRetrySends bound; compact, Kiro, Cursor and the generic OAuth hops still hold their own allowances. * docs(devlog): record the wp4 slice A audit counterexamples (#4546) * fix(responses): a consumed dispatch permit refuses the next send (#4546) Refs #4546. The single-use contract was written but not enforced: every call site discarded the boolean, so a leg that reached its thunk twice -- an adapter that calls its executor again, or a retry shape that re-enters -- got the second send for free. The return now gates the send.
…end budget (#4546) (#4611) * fix(responses): compact and the Kiro inner retries join the request send budget (#4546) Refs #4546. PRD R04. Compact held its own allowance. Its normal send took a fresh transient three, the stored-pool 401 replay added one, and the 429 alternate added another -- and the guard meant to make those last two mutually exclusive keys on kind === 'pool', so a main-pool credential left it false and really could reach five. The recursive handoff child then forwarded the options object without a holder and minted its own, so one logical compact could reach ten. It now draws the shared remainder for the ladder and spends base-then-reserve for each single send, and the handoff child inherits the holder explicitly. Kiro was the larger multiplier. It nests a three-round throttle loop over a three-attempt reset ladder that can itself run twice per round, so one adapter entry could be eighteen upstream requests, and the text-fallback rebuild constructed a fresh context that dropped whatever core passed. AdapterFetchContext now carries an optional budget, every physical send inside the reset ladder is admitted against it, and the fallback rebuild carries it forward. The field is optional and unlimited when absent so an adapter unit test that calls the transport context-free keeps its own retry shape. Deliberately still out of scope: Cursor rides IncomingMeta rather than AdapterFetchContext, the compact routed fallback mints a fresh budget, and the generic OAuth hops keep their own per-request failover counters. * style(responses): align the sendBudget field with its sibling context keys (#4546)
Refs #4546. PRD R05/wp5, first slice. The per-request budget bounds how many times one request reaches upstream. It cannot bound a fan-out: a worker that spawns seven hundred children, each sending exactly once, never violates a per-request cap and still spends the account. That is the second half of the incident. src/lib/workflow-budget.ts tracks the root workflow -- the user-visible task, identified by the parent thread header -- and gives it a finite physical-send ceiling. Every send charged to the request budget is charged to the root as well, including the cross-account move, and a root that has spent its ceiling is refused before dispatch with workflow_budget_exhausted rather than a synthetic upstream error. An exhausted root is never evicted to make room. Dropping a live entry would hand the fan-out a fresh allowance, which is precisely the laundering the ceiling exists to stop, so eviction skips any root with work in flight. The ledger is process-local and in-memory. It bounds a single proxy process honestly and says nothing about a second process sharing the same account pool; that needs a shared durable store and is declared out of scope rather than implied. The concurrency ceiling and the interactive reserve are implemented in the module but not yet wired, because they need a release path tied to the turn lease.
…4546) (#4613) Refs #4546. Forward fix for the gates failure on 1abc5cc. HandleResponsesOptions.sendBudget is typed as the narrow TransientSendBudget holder so a caller that predates the execution budget can still pass one. AdapterFetchContext needs the full contract, because an adapter that retries internally has to call reserveDispatch. Passing the narrowed value straight through failed typecheck at all three fetchResponse literals. Narrow it once next to the other budget helpers instead of asserting at each call site; an adapter that receives undefined keeps its own retry shape, which is the documented optional behaviour.
…) (#4614) Refs #4546. PRD R06/wp5, second slice, completing the module landed in #4612. A fan-out shares the conversation it serves. Without a reserve, a worker burst takes every concurrency slot under its own root and the interactive turn that started it waits behind its own children. runAdmittedHttpTurn now admits each turn against the root workflow as well as the process-wide turn gate: a request that names a parent thread distinct from its own is treated as that fan-out and may not take the reserved slots, while a top-level request is the conversation and may. The refusal is a local queue-capacity answer, not a synthetic upstream error, and the lease is released on both the normal and the throwing path so a failed turn cannot leak a slot.
…our Retry-After (#4546) (#4616) * fix(codex): promote a healthy detour instead of releasing it, and honour Retry-After (#4546) Refs #4546. PRD R07. When a transient hold outlived its window, routing deleted the whole affinity entry -- including the detour account that had actually been serving the thread -- and re-picked cold. The timer expiring restores the right to re-decide; it is not itself a recovery, and treating it as one threw away the single piece of evidence the request had. A still-healthy detour is now promoted to the binding instead, with the move recorded as rebound/transient_hold_expired so the reason is visible. A detour that has itself gone unhealthy still falls through to the cold path. Retry-After is a lower bound on the transient path. The local maximum delay bounds our own exponential backoff and has no business shortening a wait the provider stated: sending early is a request we already know will be refused, which is the storm the header exists to prevent. It is opt-in per caller so the change lands on the transient path first rather than silently lengthening every adapter's backoff, and an honoured wait is ceilinged at one minute so an hour-long Retry-After cannot park a request. * docs(devlog): record the R07 detour-promotion outcome (#4546)
Promotes the dev product snapshot 62f0222 to the preview train. The 2.55.0 line carries the #4546 cost-guard work: one send budget per logical request with a shared final-recovery reserve, zero-is-zero refusals with a typed error rather than a synthetic 502, compact and the Kiro inner retries admitted against that budget, a finite send ceiling per root workflow with an interactive reserve a fan-out cannot take, and a healthy detour promoted on transient-hold expiry instead of released cold. The previous preview tip 2.54.0-preview.20260914 is already tagged and published and is outranked by v2.54.0, so it could not be re-released; this is a new candidate rather than a re-cut.
[WRONG BRANCH] chore(release): promote 2.55.0-preview.20260914 to preview
Same product tree as preview 7bdd1b2 / 2.55.0-preview.20260914, which published successfully with its registry smoke green. Only package.json version differs.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
✅ Deterministic PR hygiene checks passed. |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (292)
📝 WalkthroughWalkthroughThis PR bundles cost-guard account-affinity routing ( ChangesCost-Guard Routing and Send-Budget Infrastructure
Provider Parity, Media, Web-Search, and Adapter Fixes
Documentation and Delivery Records
Estimated code review effort: 5 (Critical) | ~240 minutes ✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning Tools execution failed with the following error: Failed to run tools: 14 UNAVAILABLE: Connection dropped 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 |
⏳ DRAFT
What to do
Its title has been prefixed with |
리뷰 · 우선순위 76 / 80설명 이 PR은 이미 퍼블리시된 미리보기 태그 지금 로컬 본문이 말하는 2.55.0 라인의 핵심은 #4546 cost-guard 묶음입니다. 논리 요청당 하나의 send budget과 shared final-recovery reserve, 예산 0이면 typed local error( 검증 쪽은 본문 기준으로 미리보기 퍼블리시 run 34834502951( 다만 품질 게이트가 이 PR을 일반 기여 PR처럼 취급했습니다. 본문이 스스로 밝히는 한계도 중요합니다. #4546 은 아직 닫히지 않았고, durable cross-restart reservation ledger, V2 child first placement, minimum quota/cache domain contract, half-open probe lease, combo hops on shared budget, Cursor inner retries, sends-per-logical-request 표면화 등이 남아 있습니다. 정식 경로/심볼 - 문제 경로 경로 경로 enforce-target / 게이트 댓글 - 이슈 #4546 / 릴리즈 노트 범위 - 코드는 cost-guard 중간 착지를 실어 나르지만 에픽은 OPEN. 노트에 “완결”처럼 쓰면 안 되고, 본문 What this release does NOT claim 목록을 그대로 노출해야 함. CI 로컬 스위트 NOT RUN - 정책상 허용이지만, 정식 채널 승격이므로 미리보기 실퍼블리시·lifecycle 성공 증거를 릴리즈 기록에 링크해 두는 편이 안전함 (본문에 이미 run id가 있음). 메인테이너의 판단이 필요한 지점
너의 추천 병합은 이미 끝났으니 추가 코드 수정은 필요 없습니다. 바로 이어서 (1) 이 댓글은 grok-bot이 작성했습니다 |
Summary
Promotes the same product tree that shipped as
2.55.0-preview.20260914(preview tip7bdd1b29b5) to main as stable2.55.0. Onlypackage.jsonversion differs from preview; the rest of the tree is byte-identical.The 2.55.0 line carries the #4546 cost-guard work: one send budget per logical request with a shared final-recovery reserve, zero-is-zero refusals that return a typed local error rather than a synthetic 502, compact and the Kiro inner retries admitted against that budget, a finite send ceiling per root workflow with an interactive reserve a worker fan-out cannot take, and a healthy detour promoted on transient-hold expiry instead of released cold, with
Retry-Afterhonoured as a lower bound on the transient path.What this release does NOT claim
#4546 is not fully resolved. Still open: the durable cross-restart reservation ledger, V2 child first placement, the minimum quota/cache domain contract, the transient half-open probe lease, combo hops on the shared budget, Cursor's inner retries, and sends-per-logical-request surfacing. The release notes must say so rather than announcing a complete fix.
Verification
Preview at the identical tree published successfully: run 34834502951,
DRY_RUN: false, realnpm publish --tag preview, post-publish registry smoke green, GitHub Releasev2.55.0-preview.20260914at7bdd1b29b5. Preview push CI 34833399886 completed success with no failing jobs, and service lifecycle 34833399853 succeeded on the same SHA.devhas been pre-moved to 2.56.0 (#4618) so it outranks this release. Local suite NOT RUN by policy.Checklist
mainas a release promotionSummary by CodeRabbit
New Features
Bug Fixes
Documentation
Chores