Skip to content

Conversation

@jasonwbarnett
Copy link

Summary

  • Add OAuth authentication for Anthropic Claude Max/Pro subscriptions, allowing users to use their subscription billing instead of per-token API key billing
  • Uses Anthropic's OAuth code-paste flow (PKCE + S256) -- user opens auth URL, authorizes in browser, pastes back the displayed code
  • Mirrors the existing Codex (OpenAI ChatGPT) OAuth architecture for consistency

Details

New files:

  • src/common/constants/anthropicOAuth.ts -- OAuth endpoints, client ID, URL builders, system prompt prefix constant
  • src/node/utils/anthropicOauthAuth.ts -- Token parsing, expiry check (30s skew buffer)
  • src/node/services/anthropicOauthService.ts -- OAuth service: startFlow, submitCode, cancelFlow, disconnect, getValidAuth (with AsyncMutex for thread-safe refresh)

Key behaviors:

  • When OAuth is connected and no API key is set, routes through OAuth (Bearer token auth)
  • When both OAuth and API key are set, prefers API key (user explicitly configured it)
  • Wraps fetch to inject: Authorization: Bearer, anthropic-beta: oauth-2025-04-20, user-agent: claude-cli/2.1.2, tool name mcp_ prefixing, ?beta=true URL rewrite
  • Prepends Claude Code identity as a separate system text block (server validates system[0].text is exactly the identity string)
  • Strips app attribution headers (http-referer, x-title) that would identify the request as non-Claude-Code
  • Only prefixes custom tool names with mcp_; built-in server-side tools (web_search, etc.) are left unchanged
  • Prefixes tool_choice.name when tool policy forces a specific tool
  • Strips mcp_ prefix from tool names in streaming responses (with carry-over buffer for chunk boundary safety)
  • Marks model costs as included when using subscription billing
  • Auto-disconnects on invalid_grant refresh errors

UI (Settings > Anthropic):

  • "Connect with Claude" button opens auth URL in browser
  • Code paste input field with Submit button
  • Disconnect button, status/error display

Context

Anthropic has an undocumented OAuth API (same flow Claude Code CLI uses internally) that third-party tools like opencode already leverage via the opencode-anthropic-auth npm plugin. This PR brings native support to mux so users with Claude Max/Pro subscriptions don't have to pay per-token when they already have a subscription.

Replaces #2344 (moved from personal fork to org fork).

Test plan

  • make typecheck passes
  • make lint passes
  • make test -- 3653 pass (14 pre-existing failures in unrelated diffParser/hooks tests)
  • Manual: Settings > Anthropic > "Connect with Claude" > authorize in browser > paste code > verify "Connected"
  • Manual: Send message with Anthropic model using OAuth (no API key) > verify Bearer auth works
  • Manual: Disconnect > verify "Not configured" when no API key set
  • Manual: Set API key + OAuth > verify API key is preferred
  • Manual: Verify built-in tools (web_search) work without mcp_ prefix errors
  • Manual: Verify system prompt identity block passes server validation
  • Manual: Verify workspace title generation (structured outputs) works with OAuth

Generated with Claude Code

Jason Barnett and others added 5 commits February 10, 2026 16:11
Allow users to authenticate with their Claude Max/Pro subscription
instead of paying per-token via API key. Uses Anthropic's OAuth
code-paste flow (PKCE + S256).

The implementation mirrors the existing Codex (OpenAI ChatGPT) OAuth
architecture:

- New constants file with OAuth endpoints, client ID, URL builders
- AnthropicOauthService: startFlow, submitCode, token refresh with
  mutex, auto-disconnect on invalid_grant
- Provider model factory: OAuth routing decision, Bearer token
  injection, beta headers, tool name mcp_ prefixing/stripping
- Settings UI: Connect button, code paste input, disconnect
- Provider config: anthropicOauthSet flag, isConfigured override
- IPC schema + router for the OAuth flow
- Costs marked as included when using subscription billing

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Anthropic's built-in tools (web_search, code_execution, text_editor)
have versioned types like "web_search_20250305" and their names must
remain unchanged. Only prefix custom tools (type "custom" or no type)
with mcp_ when routing through OAuth.

Previously all tools were prefixed, causing the API to reject requests
with: "tools.16.web_search_20250305.name: Input should be 'web_search'"

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Anthropic's server validates that requests made with Claude Code OAuth
tokens include the Claude Code identity prefix in the system prompt.
Without it, the credential is rejected with "This credential is only
authorized for use with Claude Code." This matches the behavior of the
opencode-anthropic-auth plugin.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Three issues causing "credential only authorized for Claude Code" rejection:

1. System prompt prefix must be a SEPARATE text block at system[0],
   not concatenated with existing text. The server checks that the first
   block is exactly the Claude Code identity string.

2. App attribution headers (http-referer, x-title) identified the request
   as coming from a non-Claude-Code client. Now stripped for OAuth requests.

3. Restored beta header merging so SDK betas (structured-outputs, effort)
   are preserved alongside the required OAuth betas.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
1. Prefix tool_choice.name with mcp_ when a specific tool is forced
   via tool policy. Without this, the SDK sends { type: "tool",
   name: "bash" } while the tool definition says mcp_bash, causing
   a validation mismatch on forced-tool requests.

2. Add carry-over buffer to streaming response prefix stripping.
   Stream chunks can split a JSON token like "name":"mcp_foo" across
   reads. The carry buffer holds back trailing fragments with unclosed
   strings and prepends them to the next chunk before applying the
   regex, preventing missed replacements.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3f35d9978a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +153 to +156
const lastNewline = replaced.lastIndexOf("\n");
const tail = lastNewline >= 0 ? replaced.slice(lastNewline + 1) : replaced;
const quoteCount = (tail.match(/"/g) || []).length;
if (quoteCount % 2 !== 0) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve split tool-name fragments regardless of quote parity

In stripAnthropicOauthToolPrefix, tail carry-over is only enabled when the last SSE line has an odd quote count. If a chunk boundary lands at an even-quote point inside the pattern (for example right after "name"), the first fragment is flushed, the next fragment no longer matches /"name"\s*:\s*"mcp_..."/, and the mcp_ prefix leaks through. This makes tool-call decoding nondeterministic in streaming mode and can break tool dispatch depending on network chunking.

Useful? React with 👍 / 👎.

Comment on lines +562 to +564
if (!storedAnthropicOauth) return false;
if (!creds.isConfigured) return true;
return false;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Route Anthropic OAuth based on explicit API key presence

The OAuth routing decision uses creds.isConfigured, which includes environment-derived credentials. As a result, if a user connects Anthropic OAuth but has ANTHROPIC_API_KEY set in their shell environment (without setting anthropic.apiKey in config), requests are silently routed through API-key billing instead of Claude Max/Pro subscription billing. This is a billing-impacting behavior mismatch for OAuth users.

Useful? React with 👍 / 👎.

@ibetitsmike
Copy link
Contributor

@jasonwbarnett IIRC this is against Anthropic's terms-of-service and there was a recent ban wave for opencode users that use that OAuth. So far OpenAI & Copilot communicated it's OK to use their subscription model externally.

@jasonwbarnett
Copy link
Author

@jasonwbarnett IIRC this is against Anthropic's terms-of-service and there was a recent ban wave for opencode users that use that OAuth. So far OpenAI & Copilot communicated it's OK to use their subscription model externally.

Dang. Are you guys working with Anthropic or is this a closed door at the moment?

@ammario
Copy link
Member

ammario commented Feb 11, 2026

@jasonwbarnett unfortunately Anthropic is only permitting use with Claude Code which means some third-party tools that wrap the SDK may leverage it, but not us. I'm working on getting in touch with the right people there but hopes are low.

OpenAI's approach is a bit different in part because they have usage-based billing with Codex.

@ammario
Copy link
Member

ammario commented Feb 11, 2026

@jasonwbarnett FYI I issued you $300 in Gateway credits in appreciation for your efforts.

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.

3 participants