Description
When running Claude Code in a headless/container environment (e.g., Docker containers with ttyd web terminals), the /login flow generates an OAuth authorize URL that the claude.ai OAuth server rejects.
Environment
- Claude Code v2.1.63 (also reproduced on v2.1.42)
- Running inside Docker containers (Ubuntu 22.04) with ttyd web terminal
- Login method: Claude account with subscription (Max)
The Problem
Two issues in the generated OAuth URL when using the manual code-paste flow:
1. code=true parameter breaks authorization
The CLI adds code=true as a query parameter to the authorize URL:
https://claude.ai/oauth/authorize?code=true&client_id=...&response_type=code&...
The claude.ai OAuth server returns:
Invalid OAuth Request
Invalid response_type: missing. Expected: 'code'
This only happens in the manual code flow (headless environments). On a local machine, Claude Code uses redirect_uri=http://localhost:PORT/callback and doesn't add code=true, so login works fine.
2. org:create_api_key scope included for Claude.ai subscription login
The authorize URL includes org:create_api_key in the scope even when logging in via Claude.ai subscription (not Anthropic Console). The claude.ai OAuth server returns:
Invalid OAuth Request
Missing scope parameter
(This error also occurs when the URL is truncated due to terminal line wrapping, but the scope itself may also be rejected.)
Relevant Source Code
In cli.js (minified), the URL builder function:
j.searchParams.append("code","true"), // <-- not a standard OAuth parameter
j.searchParams.append("client_id", ...)
j.searchParams.append("response_type","code")
// ...
let J = w ? [dS] : BU1; // BU1 includes "org:create_api_key" via JHA
j.searchParams.append("scope", J.join(" "))
The scope arrays:
JHA = [jPK, "user:profile"] // jPK = "org:create_api_key"
a61 = ["user:profile", dS, "user:sessions:claude_code", "user:mcp_servers"]
BU1 = Array.from(new Set([...JHA, ...a61]))
// Result: ["org:create_api_key", "user:profile", "user:inference", "user:sessions:claude_code", "user:mcp_servers"]
Workaround
Patching cli.js to:
- Remove the
j.searchParams.append("code","true") call
- Change
JHA=[jPK,"user:profile"] to JHA=["user:profile"]
After these patches, /login works correctly in container environments — the OAuth URL is accepted, the user can authorize, and the code exchange succeeds.
Expected Behavior
/login should work in headless/container environments where the manual code-paste flow is used, without requiring source patches.
Additional Context
The manual code flow uses redirect_uri=https://platform.claude.com/oauth/code/callback which correctly displays the auth code for the user to copy-paste back into the CLI. The flow itself works — only the URL construction is broken.
Description
When running Claude Code in a headless/container environment (e.g., Docker containers with ttyd web terminals), the
/loginflow generates an OAuth authorize URL that theclaude.aiOAuth server rejects.Environment
The Problem
Two issues in the generated OAuth URL when using the manual code-paste flow:
1.
code=trueparameter breaks authorizationThe CLI adds
code=trueas a query parameter to the authorize URL:The
claude.aiOAuth server returns:This only happens in the manual code flow (headless environments). On a local machine, Claude Code uses
redirect_uri=http://localhost:PORT/callbackand doesn't addcode=true, so login works fine.2.
org:create_api_keyscope included for Claude.ai subscription loginThe authorize URL includes
org:create_api_keyin the scope even when logging in via Claude.ai subscription (not Anthropic Console). Theclaude.aiOAuth server returns:(This error also occurs when the URL is truncated due to terminal line wrapping, but the scope itself may also be rejected.)
Relevant Source Code
In
cli.js(minified), the URL builder function:The scope arrays:
Workaround
Patching
cli.jsto:j.searchParams.append("code","true")callJHA=[jPK,"user:profile"]toJHA=["user:profile"]After these patches,
/loginworks correctly in container environments — the OAuth URL is accepted, the user can authorize, and the code exchange succeeds.Expected Behavior
/loginshould work in headless/container environments where the manual code-paste flow is used, without requiring source patches.Additional Context
The manual code flow uses
redirect_uri=https://platform.claude.com/oauth/code/callbackwhich correctly displays the auth code for the user to copy-paste back into the CLI. The flow itself works — only the URL construction is broken.