[BUG] claude -p rejects just-issued OAuth token with 'invalid claims: token is expired' outside host-managed-auth env (2.1.126, macOS)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet (closest neighbours: #51047 about
--bare, #50107 aboutclaude -phanging — both distinct symptoms) - [x] This is a single bug report
- [x] I am using the latest version of Claude Code (2.1.126)
What's Wrong?
After a successful claude auth login (browser flow completes; claude auth status returns loggedIn: true, authMethod: oauth_token), claude -p immediately rejects the just-issued token with API Error: 400 invalid token: token has invalid claims: token is expired — but only when the calling environment lacks the host-managed-auth env vars (CLAUDECODE=1, CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST=1, CLAUDE_CODE_SDK_HAS_OAUTH_REFRESH=1).
Inside a Claude Code Bash-tool subprocess (env vars set), the same machine, same keychain entry, same binary version works fine. Outside (plain Terminal.app session, vars not set), the same claude -p invocation fails 100% of the time.
So the bug is: keychain-based oauth_token reads succeed locally (claude auth status confirms), but the token claims sent to the API are rejected. This makes claude -p unusable for non-interactive scripting on a freshly-logged-in machine unless the script happens to run inside a host-managed Claude session.
What Should Happen?
After claude auth login succeeds and claude auth status returns loggedIn: true, claude -p "ping" should also work — regardless of whether CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST=1 is set in the calling env. The keychain-based OAuth path should be self-sufficient.
Either:
- Fix: claim format mismatch / refresh-token-not-used in the keychain-only auth path, OR
- Fix:
claude auth statusshould validate against the API rather than just checking that a token is cached, so the user knows the token is unusable BEFORE they invokeclaude -p.
Error Messages/Logs
$ claude --version
2.1.126 (Claude Code)
$ claude auth login
# (browser flow completes successfully)
$ claude auth status
{
"loggedIn": true,
"authMethod": "oauth_token",
"apiProvider": "firstParty"
}
$ echo "ping" | claude -p --output-format text
API Error: 400 invalid token: token has invalid claims: token is expired
Even after claude auth logout && claude auth login (full reset → fresh browser flow), the just-issued token is still rejected by claude -p with the same error.
Steps to Reproduce
- macOS, Claude Code 2.1.126.
- Open Terminal.app directly (i.e., NOT from inside Claude Code's Bash tool / Claude Desktop's "open terminal"). Verify env:
env | grep -i claudeshould show noCLAUDECODE=1/CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST=1. - Confirm baseline:
claude --version→2.1.126. - Run
claude auth login. Complete browser flow. Verifyclaude auth statusreturnsloggedIn: true. - Immediately run:
echo "ping" | claude -p --output-format text - Observe:
API Error: 400 invalid token: token has invalid claims: token is expired. - Discriminator check: spawn the same command from a host-managed Claude Code Bash-tool subprocess (where
CLAUDECODE=1,CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST=1,CLAUDE_CODE_SDK_HAS_OAUTH_REFRESH=1are set in the env). Same keychain entry, same binary, sameclaude -pinvocation — works fine.
Workaround: export ANTHROPIC_API_KEY=<key> to bypass the keychain OAuth path entirely. With an API key set, claude -p works in any env.
This was caught while running skill-creator's run_eval.py (which spawns claude -p subprocesses) — a real-world non-interactive scripting use case where host-managed-auth env vars aren't naturally present.
Environment
- macOS (Darwin)
- Claude Code 2.1.126 (installed at
~/.local/bin/claude) - Shell: zsh
- Auth method: OAuth via
claude auth login(browser flow, first-party Anthropic account) - Keychain entry exists and is recent (
security find-generic-password -s "Claude Code-credentials"shows mdat = today)
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗