Read claude credential from KeyChain

This commit is contained in:
Kuranasaki@M3Max 2026-07-08 17:23:13 +07:00
parent 499d25d200
commit a65b8bb6f9

View file

@ -1,3 +1,4 @@
import { execFileSync } from "node:child_process";
import os from "node:os";
import path from "node:path";
import type {
@ -9,6 +10,7 @@ import type {
import {
bearerAuthPlugin,
findOauthTokenSet,
isRecord,
missingCandidate,
providerInternalNamePlaceholder,
providerPayload,
@ -18,6 +20,8 @@ import {
type OAuthTokenSet
} from "@ccr/core/agents/local-providers/shared";
const claudeCodeKeychainService = "Claude Code-credentials";
const claudeDefaultModels = ["claude-sonnet-4-20250514"];
const percentLimitMapping = (id: string, label: string, path: string, window: string) => ({
@ -148,6 +152,19 @@ function readClaudeCodeOauth(): OAuthTokenSet | undefined {
sourceFile
};
}
const keychainRecord = readClaudeCodeKeychainRecord();
if (keychainRecord) {
const credential = findOauthTokenSet(keychainRecord);
if (credential) {
return {
accessToken: credential.accessToken,
refreshToken: credential.refreshToken,
sourceFile: `keychain:${claudeCodeKeychainService}`
};
}
}
return undefined;
}
@ -158,3 +175,24 @@ function claudeCredentialFiles(): string[] {
path.join(os.homedir(), ".config", "claude", "credentials.json")
]);
}
// Newer macOS builds of the Claude Code CLI store credentials in the
// Keychain instead of ~/.claude/.credentials.json. Reading it triggers the
// standard macOS keychain access prompt (Allow / Always Allow); the user
// declining or the item not existing both surface as a non-zero exit here.
function readClaudeCodeKeychainRecord(): Record<string, unknown> | undefined {
if (process.platform !== "darwin") {
return undefined;
}
try {
const output = execFileSync(
"security",
["find-generic-password", "-s", claudeCodeKeychainService, "-w"],
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
);
const parsed = JSON.parse(output.trim()) as unknown;
return isRecord(parsed) ? parsed : undefined;
} catch {
return undefined;
}
}