OAuth login fails in headless/container environments (code=true and org:create_api_key scope)

Resolved 💬 3 comments Opened Mar 2, 2026 by linpap Closed Mar 5, 2026

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:

  1. Remove the j.searchParams.append("code","true") call
  2. 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.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗