--print returns "Not logged in" when $USER is absent/wrong: keychain OAuth lookup keys on $USER with no uid fallback (macOS)

Status Open
Reported on v2.1.238
Maintainer reply None cached
Activity 0 comments · opened Aug 21, 2026

Summary

On macOS with subscription (OAuth) auth stored in the login Keychain, claude --print fails with a synthetic Not logged in · Please run /login envelope whenever the USER environment variable is absent or does not match the credential's Keychain account — even though the credential is valid and the same invocation succeeds the moment USER is set correctly. The CLI appears to use $USER as the Keychain account lookup key with no fallback to the process's real uid (getpwuid / os.userInfo()).

This silently breaks any spawn context that constructs a minimal child environment: launchd agents, cron jobs, CI runners, containers, and custom agent tooling that deliberately avoids spreading the parent env. The failure mode is particularly confusing because the JSON envelope reports is_error: true with api_error_status: null and the "Not logged in" text only appears in result — it looks like an auth outage, not a missing env var.

Environment

  • Claude Code 2.1.238 (also reproduced across 2.1.23x)
  • macOS 26.6.1 (Apple Silicon)
  • Subscription (OAuth) login; credential present in the login Keychain as generic password service Claude Code-credentials, account = the macOS username
  • Interactive claude works normally in the same account

Reproduction

# Baseline — full env: succeeds
echo "Say OK" | claude --print --model sonnet --tools "" --output-format json \
  --setting-sources "" --system-prompt "Reply OK"
# → {"is_error":false, ..., "result":"OK"}

# Minimal env (HOME + PATH only, no USER): fails
echo "Say OK" | env -i HOME="$HOME" PATH=/usr/bin:/bin \
  claude --print --model sonnet --tools "" --output-format json \
  --setting-sources "" --system-prompt "Reply OK"
# → {"is_error":true, "api_error_status":null, ..., "result":"Not logged in · Please run /login"}

# Same minimal env + correct USER: succeeds
echo "Say OK" | env -i HOME="$HOME" PATH=/usr/bin:/bin USER="$(id -un)" claude --print ...
# → {"is_error":false, ..., "result":"OK"}

# Same minimal env + wrong USER: fails
echo "Say OK" | env -i HOME="$HOME" PATH=/usr/bin:/bin USER=bogus claude --print ...
# → {"is_error":true, ..., "result":"Not logged in · Please run /login"}

The bisect shows USER is used as a lookup key (wrong value fails), not a presence check, and that nothing falls back to the real uid. The macOS Security framework itself does not consult $USER for login-keychain access, so the dependency is in the CLI's choice of account string.

Expected

Credential resolution should not depend on $USER. When it is absent (or arguably even when set), derive the account from the process uid (getpwuid(getuid()) / os.userInfo().username under Node), or store/look up the credential under a fixed account string.

Actual

$USER absent or mismatched → Not logged in · Please run /login as a synthetic result despite a valid Keychain credential.

Impact seen in the wild

A downstream agent framework that spawns hooks with a deliberately constructed minimal env (HOME+PATH) had every nested claude --print call fail this way for weeks — hundreds of silent failures that looked like API/auth errors (is_error=true api_error_status=null) until bisected to the missing USER variable.

One extra wrinkle for anyone hitting this from Bun-based tooling: Bun's os.userInfo().username currently mirrors $USER instead of calling getpwuid like Node (returns "unknown" when unset), so "just set USER from userInfo()" reintroduces the bug; /usr/bin/id -un is a reliable workaround source.

View original on GitHub ↗