diff --git a/CHANGELOG.md b/CHANGELOG.md index 329236768bd..005bf63c018 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ Docs: https://docs.openclaw.ai - Security: preserve restrictive plugin-only tool allowlists, require owner access for `/allowlist add` and `/allowlist remove`, fail closed when `before_tool_call` hooks crash, block browser SSRF redirect bypasses earlier, and keep non-interactive auth-choice inference scoped to bundled and already-trusted plugins. (#58476, #59836, #59822, #58771, #59120) - Exec approvals: reuse durable exact-command `allow-always` approvals in allowlist mode so identical reruns stop prompting, and tighten Windows interpreter/path approval handling so wrapper and malformed-path cases fail closed more consistently. (#59880, #59780, #58040, #59182) - Agents/runtime: make default subagent allowlists, inherited skills/workspaces, and duplicate session-id resolution behave more predictably, and include value-shape hints in missing-parameter tool errors. (#59944, #59992, #59858, #55317) +- Providers/Anthropic: keep `claude-cli/*` auth on live Claude CLI credentials at runtime, avoid persisting stale bearer-token profiles, and suppress macOS Keychain prompts during non-interactive Claude CLI setup. (#61234) Thanks @darkamenosa. - Update/npm: prefer the npm binary that owns the installed global OpenClaw prefix so mixed Homebrew-plus-nvm setups update the right install. (#60153) Thanks @jayeshp19. - Gateway/plugin routes: keep gateway-auth plugin runtime routes on write-only fallback scopes unless a trusted-proxy caller explicitly declares narrower `x-openclaw-scopes`, so plugin HTTP handlers no longer mint admin-level runtime scopes on missing or untrusted HTTP scope headers. (#59815) Thanks @pgondhi987. - Agents/exec approvals: let `exec-approvals.json` agent security override stricter gateway tool defaults so approved subagents can use `security: "full"` without falling back to allowlist enforcement again. (#60310) Thanks @lml2468. diff --git a/extensions/anthropic/cli-auth-seam.ts b/extensions/anthropic/cli-auth-seam.ts index 7256a276404..76a6e7d92bd 100644 --- a/extensions/anthropic/cli-auth-seam.ts +++ b/extensions/anthropic/cli-auth-seam.ts @@ -4,6 +4,10 @@ export function readClaudeCliCredentialsForSetup() { return readClaudeCliCredentialsCached(); } +export function readClaudeCliCredentialsForSetupNonInteractive() { + return readClaudeCliCredentialsCached({ allowKeychainPrompt: false }); +} + export function readClaudeCliCredentialsForRuntime() { return readClaudeCliCredentialsCached({ allowKeychainPrompt: false }); } diff --git a/extensions/anthropic/cli-migration.test.ts b/extensions/anthropic/cli-migration.test.ts index 0d9b51dd83b..5f3e09efef6 100644 --- a/extensions/anthropic/cli-migration.test.ts +++ b/extensions/anthropic/cli-migration.test.ts @@ -1,17 +1,21 @@ import { describe, expect, it, vi } from "vitest"; -const readClaudeCliCredentialsForSetup = vi.hoisted(() => vi.fn()); +const { readClaudeCliCredentialsForSetup, readClaudeCliCredentialsForSetupNonInteractive } = + vi.hoisted(() => ({ + readClaudeCliCredentialsForSetup: vi.fn(), + readClaudeCliCredentialsForSetupNonInteractive: vi.fn(), + })); vi.mock("./cli-auth-seam.js", async (importActual) => { const actual = await importActual(); return { ...actual, readClaudeCliCredentialsForSetup, + readClaudeCliCredentialsForSetupNonInteractive, }; }); -const { buildAnthropicCliMigrationResult, buildClaudeCliRuntimeAuthProfile, hasClaudeCliAuth } = - await import("./cli-migration.js"); +const { buildAnthropicCliMigrationResult, hasClaudeCliAuth } = await import("./cli-migration.js"); describe("anthropic cli migration", () => { it("detects local Claude CLI auth", () => { @@ -20,23 +24,15 @@ describe("anthropic cli migration", () => { expect(hasClaudeCliAuth()).toBe(true); }); - it("builds a claude-cli runtime auth profile from native setup credentials", () => { - readClaudeCliCredentialsForSetup.mockReturnValue({ - type: "oauth", - provider: "anthropic", - access: "setup-access-token", - refresh: "refresh-token", - expires: 123, - }); + it("uses the non-interactive Claude auth probe without keychain prompts", () => { + readClaudeCliCredentialsForSetup.mockReset(); + readClaudeCliCredentialsForSetupNonInteractive.mockReset(); + readClaudeCliCredentialsForSetup.mockReturnValue(null); + readClaudeCliCredentialsForSetupNonInteractive.mockReturnValue({ type: "oauth" }); - expect(buildClaudeCliRuntimeAuthProfile()).toEqual({ - profileId: "claude-cli:default", - credential: { - type: "token", - provider: "claude-cli", - token: "setup-access-token", - }, - }); + expect(hasClaudeCliAuth({ allowKeychainPrompt: false })).toBe(true); + expect(readClaudeCliCredentialsForSetup).not.toHaveBeenCalled(); + expect(readClaudeCliCredentialsForSetupNonInteractive).toHaveBeenCalledTimes(1); }); it("rewrites anthropic defaults to claude-cli defaults", () => { diff --git a/extensions/anthropic/cli-migration.ts b/extensions/anthropic/cli-migration.ts index b0daac9c63c..64f017d2fb5 100644 --- a/extensions/anthropic/cli-migration.ts +++ b/extensions/anthropic/cli-migration.ts @@ -1,6 +1,8 @@ import type { OpenClawConfig, ProviderAuthResult } from "openclaw/plugin-sdk/provider-auth"; -import { readClaudeCliCredentialsForSetup } from "./cli-auth-seam.js"; -import { CLAUDE_CLI_AUTH_PROFILE_ID, CLAUDE_CLI_BACKEND_ID } from "./cli-shared.js"; +import { + readClaudeCliCredentialsForSetup, + readClaudeCliCredentialsForSetupNonInteractive, +} from "./cli-auth-seam.js"; const DEFAULT_CLAUDE_CLI_MODEL = "claude-cli/claude-sonnet-4-6"; type AgentDefaultsModel = NonNullable["defaults"]>["model"]; @@ -94,23 +96,12 @@ function rewriteModelEntryMap(models: Record | undefined): { }; } -export function hasClaudeCliAuth(): boolean { - return Boolean(readClaudeCliCredentialsForSetup()); -} - -export function buildClaudeCliRuntimeAuthProfile() { - const credential = readClaudeCliCredentialsForSetup(); - if (!credential) { - return undefined; - } - return { - profileId: CLAUDE_CLI_AUTH_PROFILE_ID, - credential: { - type: "token" as const, - provider: CLAUDE_CLI_BACKEND_ID, - token: credential.type === "oauth" ? credential.access : credential.token, - }, - }; +export function hasClaudeCliAuth(options?: { allowKeychainPrompt?: boolean }): boolean { + return Boolean( + options?.allowKeychainPrompt === false + ? readClaudeCliCredentialsForSetupNonInteractive() + : readClaudeCliCredentialsForSetup(), + ); } export function buildAnthropicCliMigrationResult(config: OpenClawConfig): ProviderAuthResult { diff --git a/extensions/anthropic/cli-shared.ts b/extensions/anthropic/cli-shared.ts index f999ae0837c..267d5f78b76 100644 --- a/extensions/anthropic/cli-shared.ts +++ b/extensions/anthropic/cli-shared.ts @@ -1,7 +1,6 @@ import type { CliBackendConfig } from "openclaw/plugin-sdk/cli-backend"; export const CLAUDE_CLI_BACKEND_ID = "claude-cli"; -export const CLAUDE_CLI_AUTH_PROFILE_ID = "claude-cli:default"; export const CLAUDE_CLI_MODEL_ALIASES: Record = { opus: "opus", diff --git a/extensions/anthropic/index.test.ts b/extensions/anthropic/index.test.ts index ba45f401b86..77013dde29b 100644 --- a/extensions/anthropic/index.test.ts +++ b/extensions/anthropic/index.test.ts @@ -164,13 +164,6 @@ describe("anthropic provider replay hooks", () => { config: {}, } as never); - expect(result?.profiles).toContainEqual({ - profileId: "claude-cli:default", - credential: { - type: "token", - provider: "claude-cli", - token: "setup-access-token", - }, - }); + expect(result?.profiles).toEqual([]); }); }); diff --git a/extensions/anthropic/register.runtime.ts b/extensions/anthropic/register.runtime.ts index 83a05f69200..1d282c9e2a8 100644 --- a/extensions/anthropic/register.runtime.ts +++ b/extensions/anthropic/register.runtime.ts @@ -25,11 +25,7 @@ import { cloneFirstTemplateModel } from "openclaw/plugin-sdk/provider-model-shar import { fetchClaudeUsage } from "openclaw/plugin-sdk/provider-usage"; import { readClaudeCliCredentialsForRuntime } from "./cli-auth-seam.js"; import { buildAnthropicCliBackend } from "./cli-backend.js"; -import { - buildAnthropicCliMigrationResult, - buildClaudeCliRuntimeAuthProfile, - hasClaudeCliAuth, -} from "./cli-migration.js"; +import { buildAnthropicCliMigrationResult, hasClaudeCliAuth } from "./cli-migration.js"; import { CLAUDE_CLI_BACKEND_ID } from "./cli-shared.js"; import { applyAnthropicConfigDefaults, @@ -315,12 +311,7 @@ async function runAnthropicCliMigration(ctx: ProviderAuthContext): Promise { - if (!hasClaudeCliAuth()) { + if (!hasClaudeCliAuth({ allowKeychainPrompt: false })) { ctx.runtime.error( [ 'Auth choice "anthropic-cli" requires Claude CLI auth on this host.', @@ -339,15 +330,6 @@ async function runAnthropicCliMigrationNonInteractive(ctx: { return null; } - const runtimeProfile = buildClaudeCliRuntimeAuthProfile(); - if (runtimeProfile) { - upsertAuthProfile({ - profileId: runtimeProfile.profileId, - credential: runtimeProfile.credential, - agentDir: ctx.agentDir, - }); - } - const result = buildAnthropicCliMigrationResult(ctx.config); const currentDefaults = ctx.config.agents?.defaults; const currentModel = currentDefaults?.model;