Skip to content

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

Description

@linpap

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    duplicateThis issue or pull request already exists

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions