fix(claude): fall back to macOS Keychain for credentials
On macOS, Claude Code stores credentials in the Keychain under service "Claude Code-credentials" rather than in a file. When ~/.claude/.credentials.json is absent, shell out to: security find-generic-password -s "Claude Code-credentials" -w and parse the result as the same JSON schema. ~/.claude.json holds only profile/UI metadata (oauthAccount has no token fields). expiresAt in the credentials is milliseconds. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -68,9 +68,16 @@ Rejects in manifest validation when:
|
||||
|
||||
### Host auth extraction (`contrib/claude/claude_auth.py`)
|
||||
|
||||
Claude Code stores its OAuth credentials in `~/.claude/.credentials.json`
|
||||
(not in `~/.claude.json`, which contains only UI state and profile
|
||||
metadata). The relevant fields are:
|
||||
Claude Code credential storage varies by platform:
|
||||
|
||||
- **Linux**: `~/.claude/.credentials.json`
|
||||
- **macOS**: macOS Keychain, service `"Claude Code-credentials"`
|
||||
(the file path is tried first; Keychain is the fallback when the file
|
||||
is absent)
|
||||
|
||||
`~/.claude.json` contains only UI state and profile metadata — no token.
|
||||
|
||||
The credentials JSON schema (same whether from file or Keychain):
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -83,17 +90,18 @@ metadata). The relevant fields are:
|
||||
}
|
||||
```
|
||||
|
||||
Note: `expiresAt` is in **milliseconds** (not seconds).
|
||||
`expiresAt` is in **milliseconds** (not seconds).
|
||||
|
||||
At prepare/launch time, when `forward_host_credentials: true`:
|
||||
|
||||
1. Resolve `~/.claude/.credentials.json` (via `$HOME`).
|
||||
2. Parse the JSON object.
|
||||
3. Require a `claudeAiOauth` dict.
|
||||
4. Require a non-empty `claudeAiOauth.accessToken` string.
|
||||
5. If `claudeAiOauth.expiresAt` is present as a number, divide by 1000
|
||||
and require the result to be in the future.
|
||||
6. Return only the access token to the launch path.
|
||||
1. Try `~/.claude/.credentials.json`; on macOS, if absent, run
|
||||
`security find-generic-password -s "Claude Code-credentials" -w`
|
||||
and parse its stdout as JSON.
|
||||
2. Require a `claudeAiOauth` dict.
|
||||
3. Require a non-empty `claudeAiOauth.accessToken` string.
|
||||
4. If `claudeAiOauth.expiresAt` is present, divide by 1000 and require
|
||||
the result to be in the future.
|
||||
5. Return only the access token to the launch path.
|
||||
|
||||
Errors name the missing or invalid condition and point the operator at
|
||||
`claude login`, without printing token values.
|
||||
|
||||
Reference in New Issue
Block a user