Skip to content

Feat/anthropic oauth subscription - #27

Merged
ayan-de merged 5 commits into
mainfrom
feat/anthropic-oauth-subscription
Sep 5, 2026
Merged

Feat/anthropic oauth subscription#27
ayan-de merged 5 commits into
mainfrom
feat/anthropic-oauth-subscription

Conversation

@ayan-de

Copy link
Copy Markdown
Owner

No description provided.

ayan-deand others added 5 commits September 5, 2026 14:10
Reach Claude Pro/Max subscription inference instead of a metered API key.
Spec: docs/superpowers/specs/2026-09-05-anthropic-oauth-provider.md.
The subscription endpoint only answers requests that look like Claude Code,
so this impersonates it: Claude Code's OAuth client id, User-Agent, beta
headers, billing-attribution block and identity block. Read §0.1 before
extending — the account at risk is the user's own, which is why this is
opt-in and never a default, and why the impersonation is quarantined to the
OAuth path by an invariant with a test.
The seam is a composed fetch rather than the SDK's apiKey: we already wrap
fetch for timeouts, and per-request is where token refresh has to live.
Refresh is single-flight and rotation-aware. Anthropic rotates refresh
tokens, so two concurrent refreshes can persist a dead one and permanently
break the login; before refreshing we re-read the store and defer to a
fresher token rather than racing it.
An "OAuth not allowed for this organization" 403 is detected at the fetch —
generateText throws it while streamText emits it as a chunk, but the fetch
sees the same raw body on both paths — then latches for the process and
falls back to the API key. The latch gates usesAnthropicOAuth() itself, so
the fallback request also drops the identity block: a key-authenticated
request must never claim to be Claude Code.
Cost: a subscription call prices as undefined, never $0, or rollups would
report fake savings. The mode is stamped on model.response and folded into
ModelSpan, so cost is a property of the CALL — reading live config at fold
time repriced every historical API-key session as "subscription" the moment
someone logged in, and made a span's price depend on which machine read the
log. pricing.ts no longer imports config.ts.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cy5ynPzdM4gJ8o1qKpp5uX
…iption
Phase 1 of the OAuth spec: our own PKCE login, so freecode works on a machine
that never ran Claude Code (Phase 0 could only borrow that CLI's credentials).
Authorize against the claude.ai surface, NOT the console one — the console
endpoint mints tokens that refresh fine and are then refused at inference
time, which is a silent failure hours later rather than a login error.
state IS the PKCE verifier (jcode's convention: the token endpoint requires a
state, and reusing the verifier binds it to the PKCE secret). That makes login
inherently single-process, so there is deliberately no --code flag: a fresh
process has a fresh verifier and its exchange could never succeed.
The callback listener binds an ephemeral 127.0.0.1 port; failing to bind is
not an error but a downgrade to the manual redirect URI and a paste prompt,
which is also what --no-browser selects. Paste accepts what a user can
plausibly have in hand: a bare code, a callback URL, a query string, or
code#state.
Login prints the §0.1 disclosure and pins authMode to oauth; logout deletes
the tokens and un-pins it. Pinning is what makes an explicit login stick on a
machine that also has ANTHROPIC_API_KEY set, where the unpinned fallback
prefers the key.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cy5ynPzdM4gJ8o1qKpp5uX
…e switch
OAuth spec §8. The subscription endpoint sends a different beta set and two
extra system blocks, so an OAuth run is not the same instrument as an API-key
run. Without this, one subscription run silently became the bar every later
API-key run was measured against.
An absent mode on either side normalises to api-key: every baseline written
before this landed has no mode, and treating that as a mismatch would discard
all of them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cy5ynPzdM4gJ8o1qKpp5uX
OAuth spec §3.5 asked for these shapes to be checked rather than assumed.
They are covered — by the sk-ant- prefix pattern alone: the generic
token[:=]value pattern does NOT match a JSON-quoted key ("access_token": …),
so if Anthropic ever changes the prefix, this test is what fails instead of
tokens quietly reaching the embedding index.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cy5ynPzdM4gJ8o1qKpp5uX
Points at the spec and states the load-bearing bits an agent needs before
touching this: the opt-in paths, that login is single-process, where the
billing block sits, that cost is stamped on the call, and that tool-name
mapping stays unbuilt until a real turn answers §9 Q1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cy5ynPzdM4gJ8o1qKpp5uX
@vercel

vercelBot commented Sep 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated
freecode-docsReadyReadyPreviewSep 5, 2026 9:12am UTC
freecode-webReadyReadyPreviewSep 5, 2026 9:12am UTC

@ayan-de
ayan-de merged commit 360d1e5 into mainSep 5, 2026
4 checks passed
Sign up for freeto 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.

1 participant

@ayan-de