Repository navigation
fix(anthropic-subscription): retry on the account that served the session, not the last one tried - #4318
Open
code-yeongyu wants to merge 3 commits into
Open
fix(anthropic-subscription): retry on the account that served the session, not the last one tried#4318code-yeongyu wants to merge 3 commits into
code-yeongyu wants to merge 3 commits into
Conversation
… an unanswered attempt elsewhere A failed, discarded or uncertainly aborted attempt on account B published B's retry checkpoint as the session's binding, even though B never served the session. The binding names the account the next request prefers and the account the seat-change flag compares against. So once A, the account that served the session and holds its prompt cache, recovered, the retry went to B and wrote the whole conversation to an account that had never cached it. When B was still blocked, the retry went back to A and read A's cache, but it was logged as account_changed with seatChanged true. createSessionTurnAttempt now takes the binding from before the attempt. When an unanswered attempt on another account has no process left, it re-publishes that binding, minus a fork boundary Claude Code rejected, instead of its own checkpoint. The next request prefers the serving account as soon as it can serve, forks that account's own lineage, and is logged as a same-seat timeout_retry. A process kept after an abort keeps its own checkpoint, as before. Fixes #4304 Part of code-yeongyu/oh-my-openagent#10304
code-yeongyu
marked this pull request as ready for review
October 26, 2026 02:43
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4304
Part of code-yeongyu/oh-my-openagent#10304
Problem
A prompt cache lives on the account that served the session. When every attempt of a turn failed (A rate-limited, then B rate-limited, stalled or failed), the failed attempt on B published B's retry checkpoint as the session's binding, although B had never served the session. The binding is what the next request prefers (
preferredAccount) and what the seat-change flag compares against (lineageSeat). So:fork / account_changedwithseatChanged: true(Retry log marks a seat change when returning to the cache-holding account #4304).Change
session-turn-attempt.ts:createSessionTurnAttempttakesservedLineage, the session's binding from before the attempt. Once the attempt's process is gone (a failure, a discard, or an abort that closed it), an unanswered attempt on an account other than the one that binding names re-publishes that binding instead of its own retry checkpoint. If Claude Code rejected a fork boundary during the attempt, that boundary is dropped from the binding first, as [Bug]: #1472 still causes repeated full-history replay and stale UUID resume fallback #1958 already does for the attempt's own lineage.session-stream.ts:createResidentAttemptreads the binding after restored-binding admission and passes it on.Account selection does not change. It already prefers the binding's account, and with the binding now kept on the serving account, that account is preferred as soon as it can serve, before any cold account. A real move is still labelled
account_changedwithseatChanged: true, and the new account keeps the session after the old one recovers.Test authoring gate:
timeout_retry. A move while that account stays down is still logged as a seat change, and the new account then keeps the session.ANTHROPIC_BASE_URL, so the test adds it at the SDK boundary. A fake clock (overrideAuthLaneBoundary({ now })) expires the rate-limit blocks. Claude Code is spawned throughspawnClaudeCodeProcess, so cleanup waits for each process to exit before removing its directory. The kept-process case drives the real session pump with a scripted query, because the shipped Claude Code closes the process when a stalled attempt is interrupted (see Evidence).Evidence
main(44eccff) with the new tests: 4 failed, 4 passed.A:200, A:429, B:429, B:200), a cold write on an account that never served the session.fork / account_changed,seatChanged: true.B:200).main.lastAssistantUuid).bf5318ed8f:bun run checkexited 0. The 64 test files that drive the resident session path passed underCI=1(64 files, 404 tests), and the conflict-marker check exited 0.