anthropics / anthropics/claude-code

[BUG] Keychain and ~/.claude/.credentials.json diverge: orphaned plaintext credential causes false-healthy diagnosis

Abierto
#91,180 0 comentarios 0 reacciones 0 asignados Ver en GitHub
area:auth area:security bug platform:macos
Lenguaje dominante
Python
Estrellas
145k
Forks
23.1k
Métricas de merge de PR
Métricas de PR pendientes

Descripción

## 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).

Guía de contribución

No hay ninguna guía de contribución indexada para este repositorio

Línea de trabajo

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.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
macos, python
Área
authentication, cli, security
Tipo de issue
Error
Dificultad
5/5
Tiempo estimado
Más de una semana
Estado de actividad
Activo
Claridad
Bastante claro
Aptitud para principiantes
35/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.