OAuth token exchange fails with HTTP 400 on fresh /login and setup-token (VS Code Remote Tunnel to Linux)
Environment
- Claude Code version: 2.1.271 → reproduced again on 2.1.273 after auto-update
- Client: VS Code extension, accessed via
vscode.dev/tunnel/<host>(VS Code Remote Tunnels) to a headless Linux (NixOS) server - Account: personal Pro/Max subscription (not Team/Enterprise — so this is not the admin-toggle case)
- Also reproduced identically from a plain integrated terminal on the same host running
claudedirectly (not just inside the VS Code extension webview)
What happened
Starting Remote Control (/remote-control in the VS Code extension) surfaced:
Remote Control error: Claude.ai login was rejected — run /login, then /remote-control
Running /login (or a completely fresh claude process, or claude setup-token) goes through the browser OAuth consent screen successfully (Authorize works, browser is redirected with a valid-looking code+state in the query string), but the redirect target http://localhost:<port>/callback?code=...&state=... is unreachable from the browser (expected/documented behavior for SSH/remote-tunnel setups — browser and Claude Code process are on different machines).
Manually pasting code#state (or code=...&state=...) into the terminal's Paste code here if prompted > prompt — copied immediately from the address bar, no delay — consistently returns:
OAuth error: Request failed with status code 400
Press Enter to retry.
This reproduces:
- With a fresh
claudeprocess each time (not just repeated "Enter to retry" — ruled out stale in-process PKCE state) - With
claude setup-tokenas well as/login(ruled out this being specific to the Remote Control scope set — same 400 either way) - With the code pasted within seconds of being issued (ruled out code expiry)
- With system clock verified accurate against an external time source (ruled out clock skew as the invalid_grant cause)
- With no
ANTHROPIC_API_KEY,ANTHROPIC_BASE_URL,DISABLE_TELEMETRY,DO_NOT_TRACK,CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC, orDISABLE_GROWTHBOOKset in the environment
An already-established login on the same machine (a long-running Claude Code session started earlier) continues to work fine — this only affects new OAuth token exchanges from this host.
Expected
Either the token exchange succeeds, or the terminal surfaces a specific reason for the 400 (expired/consumed code, PKCE mismatch, org policy, etc.) instead of a bare status code.
Additional context
The generic claude doctor (shell) diagnostic didn't surface anything related to this; it only reported an unrelated install/PATH note.
Happy to provide a debug log (--debug-file) or any other diagnostic on request.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗