Skip to content

fix(oauth): serialize interactive authorization per provider session - #253

Merged
steipete merged 1 commit into
mainfrom
fix/oauth-authorization-singleflight
Aug 2, 2026
Merged

fix(oauth): serialize interactive authorization per provider session#253
steipete merged 1 commit into
mainfrom
fix/oauth-authorization-singleflight

Conversation

@steipete

@steipete steipete commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #247 with the decided join-in-flight contract: one interactive OAuth authorization transaction per shared provider/session.

Concurrent SDK auth() flows sharing one provider (the background Streamable HTTP GET reconnect and a bridged POST both receiving 401) each generated a PKCE challenge and opened a prompt, while only one persisted verifier could complete.

What changed

  • PersistentOAuthClientProvider.redirectToAuthorization now claims a single interactive transaction per provider (synchronously, before any await). Overlapping redirects are suppressed: no second browser prompt, no second onAuthorizationUrl.
  • saveCodeVerifier remembers each verifier by its S256 challenge; the claiming redirect re-persists its own flow's verifier, so a concurrent flow's save can never clobber the completable transaction.
  • The claim clears when the callback settles (code received, OAuth error, invalid state, or session close), when the verifier is invalidated, or after a 300s abandonment TTL mirroring the authorization-code timeout — so a lost browser tab cannot suppress prompts forever.
  • Keep-alive daemon operations no longer restart and replay after unauthorized errors (shouldRestartDaemonServer now consults isUnauthorizedError): a restart cannot repair a credential, and the replay was a second source of duplicate prompts.
  • Also fixes a strict-typecheck error in tests/oauth-open-external.test.ts that was breaking pnpm check on main (CI's build job only compiles tsconfig.build.json, which excludes tests).

Tests

  • tests/oauth-bridge-get-post-concurrency.test.ts — the reporter's full regression harness from Serialize interactive OAuth authorization per shared provider/session #247, verbatim apart from strict-TS annotations. Fails on unpatched main with authorizationCount: 2, uniqueChallenges: 2; passes with this change: one flow, synthetic completion succeeds, the retried Codex-shaped tools/list returns ping.
  • tests/oauth-session.test.ts — unit coverage for the single-flight semantics: one prompt for overlapping flows, persisted verifier matches the claimed challenge, pending saves cannot clobber it, and completion re-arms normal prompting.
  • tests/keep-alive-runtime.test.ts — unauthorized daemon errors neither restart the server nor replay the operation.

Proof (Node 24.18.0 / pnpm 10.33.2, macOS)

  • pnpm check — format, type-aware lint, typecheck clean.
  • pnpm build — clean.
  • pnpm test — 904 passed / 3 skipped.
  • Codex AutoReview (gpt-5.6-sol, high, branch vs origin/main) — clean, no accepted/actionable findings.

Thanks @mkaput for the exceptional report — the regression harness made this implementable without guesswork.

🤖 Generated with Claude Code

Concurrent SDK auth() flows sharing one provider (background Streamable
HTTP GET reconnect + bridged POST both receiving 401) each generated a
PKCE challenge and opened a prompt, while only one persisted verifier
could complete. redirectToAuthorization now claims a single interactive
transaction per provider, suppresses overlapping redirects, re-persists
the claimed flow's verifier, and clears the claim when the callback
settles (with a 300s abandonment TTL). Keep-alive daemon operations no
longer restart and replay on unauthorized errors, which duplicated
authorization prompts.

Fixes #247

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@steipete
steipete merged commit e496617 into main Aug 2, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Serialize interactive OAuth authorization per shared provider/session

1 participant