anthropics / anthropics/claude-code
[BUG] Keychain and ~/.claude/.credentials.json diverge: orphaned plaintext credential causes false-healthy diagnosis
- Langage dominant
- Python
- Étoiles
- 145k
- Forks
- 23.1k
- Métriques de merge des PR
- Métriques de PR en attente
Description
## What's Wrong?
On macOS, Claude Code reads OAuth credentials from the **Keychain**, but a plaintext
`~/.claude/.credentials.json` can persist containing a *complete, unexpired* credential
that the client never uses for authentication. The two stores diverge silently.
Result: the client correctly reports "Not logged in", while a file on disk shows a valid
Max subscription credential with full scopes. Every human and every tool that inspects
that file concludes authentication is fine.
Observed on this machine:
```
Keychain "Claude Code-credentials" top-level keys: ['mcpOAuth']
claudeAiOauth: ABSENT
~/.claude/.credentials.json top-level keys: ['claudeAiOauth']
subscriptionType: max
scopes: user:file_upload, user:inference,
user:mcp_servers, user:profile,
user:sessions:claude_code
expiresAt: unexpired
refreshTokenExpiresAt: +27 days
accessToken: present (108 chars)
```
Two distinct problems follow:
**1. Diagnosability.** The plaintext file is the obvious place to look and it reports a
false pass. This cost roughly six hours of misdirected debugging, during which the
correct advice ("run /login") was actively avoided *because* the file looked healthy.
**2. Security.** An orphaned plaintext file retains a live refresh token with 27 days of
validity that the client will never use, never refresh, and never clean up. A Keychain
item requires per-application ACL consent; a `0600` file requires only executing as the
user. The credential outlives its own usefulness in the weaker of the two stores.
## What Should Happen?
1. When the client authenticates via Keychain, any stale `~/.claude/.credentials.json`
should be removed or reconciled — an orphaned live refresh token should not persist.
2. `claude auth status` should report **which store** the credential was read from, so
divergence is visible rather than silent.
3. When no usable credential is found in the Keychain but a credential exists in the
plaintext file, say so explicitly. "Not logged in" is accurate but gives the user no
path to the actual problem.
## Error Messages/Logs
```
$ claude --debug-to-stderr -p "say ok"
[DEBUG] [Bootstrap] Skipped: no usable OAuth, WIF, or API key
[ERROR] API error (attempt 1/11): Could not resolve authentication method.
Expected one of apiKey, authToken, credentials, config, or profile to be set.
Not logged in · Please run /login
```
Simultaneously, the startup banner renders `Welcome back ` and the account's
organization as `claude_max`, sourced from `oauthAccount` in `~/.claude.json` — a third
store that still holds the profile. Three stores, three different answers.
## Steps to Reproduce
Exact trigger for the divergence is not established. Observed state after normal use
(the machine was authenticated and working the previous day):
1. Inspect the Keychain item the client reads:
```
security find-generic-password -s "Claude Code-credentials" -w \
| python3 -c "import sys,json;print(sorted(json.load(sys.stdin).keys()))"
```
Observed: `['mcpOAuth']` — no `claudeAiOauth`.
2. Inspect the plaintext store:
```
python3 -c "import json,os;print(sorted(json.load(open(os.path.expanduser('~/.claude/.credentials.json'))).keys()))"
```
Observed: `['claudeAiOauth']`, unexpired, 5 scopes, subscriptionType `max`.
3. Run any command requiring auth. Observe "Not logged in" despite step 2.
## Additional Information
**Note on `claude setup-token` as a workaround:** the binary states these tokens are
"limited to inference-only for security reasons", so they do not restore
`user:sessions:claude_code` and will not fix `claude --teleport`. `claude auth login
--claudeai` is the correct remedy.
**Related:** #91158 (this issue was split out of a correction to that report; the
"client misreports auth mode" claim there is withdrawn in favor of this).
Guide de contribution
Aucun guide de contribution indexé pour ce dépôt
Piste de recherche
Start by reproducing the macOS state with the provided Keychain command and by inspecting ~/.claude/.credentials.json and ~/.claude.json. Trace the authentication path used by `claude auth status` and normal authenticated commands, then define tests for store divergence, stale plaintext cleanup or reconciliation, and reporting the credential source. Done means the client no longer leaves an unused live credential silently and explains which store is authoritative or why login is required.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- macos, python
- Domaine
- authentication, cli, security
- Type d'issue
- Bug
- Difficulté
- 5/5
- Temps estimé
- Plus d'une semaine
- Activité
- Active
- Clarté
- Plutôt claire
- Accessibilité débutants
- 35/100