fix(auth): keep provider-owned Claude OAuth pools intact on login - #1309
Merged
Conversation
A provider whose own login returns the complete pooled credential was double-pooled: appendLoginSlot read that result's top-level fields as a flat credential and appended them as a second slot. For claude-sdk-oauth those fields are the managed sentinel, so a second account produced a login-2 slot holding placeholder tokens. Adopts the slots.ts hunk from #1304 and #1196 by @eddieparc. Ultraworked with [omo](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
…d account Shared credential rotation projects one named slot onto the flat credential shape and strips accounts, so the availability predicate counted zero accounts and reported "Provider is not configured: claude-sdk-oauth". A projected sentinel still counts as zero, leaving the ambient opt-in path unchanged. Adopts the oauth-login.ts hunk from #1304 and #1196 by @eddieparc. Ultraworked with [omo](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
Ultraworked with [omo](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
code-yeongyu
force-pushed
the
fix/claude-sdk-oauth-login-pool
branch
from
September 3, 2026 03:35
ca17abf to
9034138
Compare
This was referenced Sep 3, 2026
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.
Summary
Adding a second
claude-sdk-oauthaccount produced a brokenlogin-2slot holding the managed placeholderclaude-sdk-oauth-managedinstead of the newly issued OAuth tokens. The provider's ownloginalready returns the complete pooled credential, but the sharedappendLoginSlotwrite step read that result's top-level fields as if they were a flat credential and appended them a second time. This PR makesappendLoginSlotwrite a provider-owned pool through untouched, and teaches the claude-sdk-oauth availability predicate to accept a concrete OAuth account projected by shared credential rotation.Observably: a second login now stores exactly one new slot carrying its real tokens, that slot participates in affinity and failover, and the second login no longer fails with
Provider is not configured: claude-sdk-oauth. First logins,api_keylogins, and the ambient (host Claude CLI) lane behave exactly as before.Changes
Shared credential pool (
packages/ai)appendLoginSlotreturns the login result unchanged when that result already carries a populatedaccountsarray — a provider that manages its own pool produces the complete post-login credential, so re-appending its top-level fields is double pooling. Every other branch is byte-identical: an absent or flatcurrentstill stores the flat credential as-is, and an unnamed flat credential against a pooledcurrentstill becomes the nextlogin-Nslot with its own material.claude-sdk-oauth availability (
packages/coding-agent)configuredFor, the single predicate behind bothcheckandresolveAmbient, now counts a stored credential whose top-level OAuth fields are concrete (neitheraccessnorrefreshis the sentinel) as one account. Slot-scoped resolution projects one named slot onto the flat credential shape and stripsaccounts, so such a credential previously counted as zero accounts. A projected sentinel still counts as zero, leaving the ambient opt-in requirement untouched.Tests and docs
packages/ai/test/credential-pool-mutations.test.ts; projected-slot and projected-sentinelcheckcases inpackages/coding-agent/test/claude-sdk-oauth-login.test.ts.packages/ai/src/changes.mdand.../claude-sdk-oauth/changes.md, plus[Unreleased] > Fixedbullets in both package changelogs.QA & Evidence
All test, typecheck, and build runs executed on a remote bunshin machine (
mengmotaMac, bun 1.4.0, node v26.7.0). Evidence lives under/Users/yeongyu/sisyphuslabs/.omo/evidence/ulw/claude-sdk-fable51-20260903.9892d3eb2sourceslogin-2holdsclaude-sdk-oauth-managed;checkreturnsundefined) — no import errorsg4-red.logpackages/ai18/18,packages/coding-agent8/8g4-green.logappendLoginSlotearly return kills only the #1279 test; removingselectedStoredAccountkills only the projected-slot test; restoring both is green againg4-mutation.loglogin-2with its own tokens; flatapi_keystill appends;appendLoginSlot(undefined, flat)returns identity; projected sentinel still rejected — 14/14 + 8/8g4-edge.log--build:tsc --noEmit -p tsconfig.build.jsonin both packages plus the credential-pool, auth, and claude-sdk-oauth suites (login, accounts, auth-lane, availability, all ambient, extension, account-command)cmd-exit=0g4-regression.lognode scripts/check-pr-changelog.mjs --base 9892d3eb2changelog-gate: PASSg4-lane-report.mdThe mutation pass is what makes this sufficient: each of the two production hunks has exactly one test that dies without it, so neither guard is passing by accident, and the edge run proves the untouched branches of
appendLoginSlotstill behave as before.Note: neither package ships a plain
tsconfig.json; both typechecks usetsconfig.build.json.Risks & Residuals
loginreturns a populatedaccountsarray now owns its entire pool write.appendLoginSlot's only callers areModelsImpl.loginandAuthStorage.set, and claude-sdk-oauth is the only in-tree provider that returns a pooled login result, so no other provider's behavior changes.openai-codex) is a separate concern and stays open.Related Issues
packages/ai/src/auth/pool/slots.tsandclaude-sdk-oauth/oauth-login.tshunks from his PRs fix(auth): keep Claude OAuth named pools and stop provider hops #1304 and fix(auth): harden Claude OAuth credential pools #1196 (fix(auth): keep Claude OAuth named pools and stop provider hops #1304'sin-narrowing form was preferred over fix(auth): harden Claude OAuth credential pools #1196'sas PooledCredentialcast to satisfy the no-asdiscipline). The other hunks in fix(auth): keep Claude OAuth named pools and stop provider hops #1304 —agent-session.ts,retry.ts,session-continuity.ts,login-dialog.ts— are deliberately not part of this PR.Summary by cubic
Adding a second
claude-sdk-oauthaccount no longer produces a brokenlogin-2slot holding the managed placeholder, and that slot no longer fails withProvider is not configuredonce rotation selects it. The shared pool writer now passes a provider-owned pooled login result through untouched, and the claude-sdk-oauth availability predicate counts a rotation-projected concrete slot as one configured account.appendLoginSlotreturns unchanged any login result that already carries a populatedaccountsarray; first logins, flatapi_keylogins, and unnamed-slot writes keep their existing behavior.Written for commit 9034138. Summary will update on new commits.