mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:20:43 +00:00
fix: sync Claude CLI OAuth credentials (#70902) (thanks @starvex)
This commit is contained in:
@@ -79,6 +79,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Daemon/service-env: add Nix Home Manager profile bin directories to generated gateway service PATHs on macOS and Linux, honoring `NIX_PROFILES` right-to-left precedence and falling back to `~/.nix-profile/bin` when unset. Fixes #44402. (#59935) Thanks @jerome-benoit.
|
||||
- Agents/heartbeat: stop injecting the heartbeat system prompt into non-heartbeat runs, preventing ordinary user replies from being suppressed as `HEARTBEAT_OK` acknowledgments. Fixes #69079. (#69278) Thanks @stainlu.
|
||||
- Active Memory: keep silent recall sub-agent billing/auth failures out of shared auth-profile cooldown state, so a Claude CLI extra-usage rejection cannot disable normal Claude-backed turns. Fixes #71284. (#71539) Thanks @vishutdhar and @obviyus.
|
||||
- Auth/Claude CLI: sync refreshed Claude CLI OAuth credentials into the managed auth profile so long-running Claude CLI runs stop falling back to stale OpenClaw snapshots. (#70902) Thanks @starvex.
|
||||
|
||||
## 2026.4.25 (Unreleased)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ const { readCodexCliCredentialsCachedMock } = vi.hoisted(() => ({
|
||||
}));
|
||||
|
||||
vi.mock("./cli-credentials.js", () => ({
|
||||
readClaudeCliCredentialsCached: () => null,
|
||||
readCodexCliCredentialsCached: readCodexCliCredentialsCachedMock,
|
||||
readMiniMaxCliCredentialsCached: () => null,
|
||||
resetCliCredentialCachesForTest: () => undefined,
|
||||
|
||||
@@ -21,6 +21,7 @@ vi.mock("../plugins/provider-runtime.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("./cli-credentials.js", () => ({
|
||||
readClaudeCliCredentialsCached: () => null,
|
||||
readCodexCliCredentialsCached: () => {
|
||||
const codexHome = process.env.CODEX_HOME;
|
||||
if (!codexHome) {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { AuthProfileStore, OAuthCredential } from "./auth-profiles/types.js";
|
||||
import type { ClaudeCliCredential } from "./cli-credentials.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
readClaudeCliCredentialsCached: vi.fn<() => ClaudeCliCredential | null>(() => null),
|
||||
readCodexCliCredentialsCached: vi.fn<() => OAuthCredential | null>(() => null),
|
||||
readMiniMaxCliCredentialsCached: vi.fn<() => OAuthCredential | null>(() => null),
|
||||
}));
|
||||
@@ -12,6 +14,7 @@ let hasUsableOAuthCredential: typeof import("./auth-profiles/external-cli-sync.j
|
||||
let isSafeToUseExternalCliCredential: typeof import("./auth-profiles/external-cli-sync.js").isSafeToUseExternalCliCredential;
|
||||
let shouldBootstrapFromExternalCliCredential: typeof import("./auth-profiles/external-cli-sync.js").shouldBootstrapFromExternalCliCredential;
|
||||
let shouldReplaceStoredOAuthCredential: typeof import("./auth-profiles/external-cli-sync.js").shouldReplaceStoredOAuthCredential;
|
||||
let CLAUDE_CLI_PROFILE_ID: typeof import("./auth-profiles/constants.js").CLAUDE_CLI_PROFILE_ID;
|
||||
let OPENAI_CODEX_DEFAULT_PROFILE_ID: typeof import("./auth-profiles/constants.js").OPENAI_CODEX_DEFAULT_PROFILE_ID;
|
||||
let MINIMAX_CLI_PROFILE_ID: typeof import("./auth-profiles/constants.js").MINIMAX_CLI_PROFILE_ID;
|
||||
|
||||
@@ -42,9 +45,11 @@ describe("external cli oauth resolution", () => {
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
vi.doMock("./cli-credentials.js", () => ({
|
||||
readClaudeCliCredentialsCached: mocks.readClaudeCliCredentialsCached,
|
||||
readCodexCliCredentialsCached: mocks.readCodexCliCredentialsCached,
|
||||
readMiniMaxCliCredentialsCached: mocks.readMiniMaxCliCredentialsCached,
|
||||
}));
|
||||
mocks.readClaudeCliCredentialsCached.mockReset().mockReturnValue(null);
|
||||
mocks.readCodexCliCredentialsCached.mockReset().mockReturnValue(null);
|
||||
mocks.readMiniMaxCliCredentialsCached.mockReset().mockReturnValue(null);
|
||||
({
|
||||
@@ -55,7 +60,7 @@ describe("external cli oauth resolution", () => {
|
||||
shouldBootstrapFromExternalCliCredential,
|
||||
shouldReplaceStoredOAuthCredential,
|
||||
} = await import("./auth-profiles/external-cli-sync.js"));
|
||||
({ OPENAI_CODEX_DEFAULT_PROFILE_ID, MINIMAX_CLI_PROFILE_ID } =
|
||||
({ CLAUDE_CLI_PROFILE_ID, OPENAI_CODEX_DEFAULT_PROFILE_ID, MINIMAX_CLI_PROFILE_ID } =
|
||||
await import("./auth-profiles/constants.js"));
|
||||
});
|
||||
|
||||
@@ -302,6 +307,43 @@ describe("external cli oauth resolution", () => {
|
||||
expect(credential).toBeNull();
|
||||
});
|
||||
|
||||
it("normalizes Claude CLI oauth credentials into the managed Claude profile", () => {
|
||||
mocks.readClaudeCliCredentialsCached.mockReturnValue({
|
||||
type: "oauth",
|
||||
provider: "anthropic",
|
||||
access: "claude-cli-access",
|
||||
refresh: "claude-cli-refresh",
|
||||
expires: Date.now() + 5 * 24 * 60 * 60_000,
|
||||
});
|
||||
|
||||
const profiles = resolveExternalCliAuthProfiles(makeStore());
|
||||
|
||||
expect(profiles).toEqual([
|
||||
{
|
||||
profileId: CLAUDE_CLI_PROFILE_ID,
|
||||
credential: expect.objectContaining({
|
||||
type: "oauth",
|
||||
provider: "claude-cli",
|
||||
access: "claude-cli-access",
|
||||
refresh: "claude-cli-refresh",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("ignores Claude CLI token credentials", () => {
|
||||
mocks.readClaudeCliCredentialsCached.mockReturnValue({
|
||||
type: "token",
|
||||
provider: "anthropic",
|
||||
token: "claude-cli-token",
|
||||
expires: Date.now() + 5 * 24 * 60 * 60_000,
|
||||
});
|
||||
|
||||
const profiles = resolveExternalCliAuthProfiles(makeStore());
|
||||
|
||||
expect(profiles).toEqual([]);
|
||||
});
|
||||
|
||||
it("resolves fresher minimax external oauth profiles as runtime overlays", () => {
|
||||
mocks.readMiniMaxCliCredentialsCached.mockReturnValue(
|
||||
makeOAuthCredential({
|
||||
|
||||
@@ -4,6 +4,7 @@ import path from "node:path";
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("./cli-credentials.js", () => ({
|
||||
readClaudeCliCredentialsCached: () => null,
|
||||
readCodexCliCredentialsCached: () => null,
|
||||
readMiniMaxCliCredentialsCached: () => null,
|
||||
}));
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import {
|
||||
readClaudeCliCredentialsCached,
|
||||
readCodexCliCredentialsCached,
|
||||
readMiniMaxCliCredentialsCached,
|
||||
} from "../cli-credentials.js";
|
||||
import {
|
||||
CLAUDE_CLI_PROFILE_ID,
|
||||
EXTERNAL_CLI_SYNC_TTL_MS,
|
||||
MINIMAX_CLI_PROFILE_ID,
|
||||
OPENAI_CODEX_DEFAULT_PROFILE_ID,
|
||||
@@ -91,6 +93,17 @@ const EXTERNAL_CLI_SYNC_PROVIDERS: ExternalCliSyncProvider[] = [
|
||||
readCredentials: () => readCodexCliCredentialsCached({ ttlMs: EXTERNAL_CLI_SYNC_TTL_MS }),
|
||||
bootstrapOnly: true,
|
||||
},
|
||||
{
|
||||
profileId: CLAUDE_CLI_PROFILE_ID,
|
||||
provider: "claude-cli",
|
||||
readCredentials: () => {
|
||||
const credential = readClaudeCliCredentialsCached({ ttlMs: EXTERNAL_CLI_SYNC_TTL_MS });
|
||||
if (credential?.type !== "oauth") {
|
||||
return null;
|
||||
}
|
||||
return { ...credential, provider: "claude-cli" };
|
||||
},
|
||||
},
|
||||
{
|
||||
profileId: MINIMAX_CLI_PROFILE_ID,
|
||||
provider: "minimax-portal",
|
||||
|
||||
@@ -15,6 +15,7 @@ const readCodexCliCredentialsCachedMock = vi.hoisted(() =>
|
||||
);
|
||||
|
||||
vi.mock("../cli-credentials.js", () => ({
|
||||
readClaudeCliCredentialsCached: () => null,
|
||||
readCodexCliCredentialsCached: readCodexCliCredentialsCachedMock,
|
||||
readMiniMaxCliCredentialsCached: () => null,
|
||||
}));
|
||||
|
||||
@@ -13,6 +13,7 @@ export function getOAuthProviderRuntimeMocks() {
|
||||
}
|
||||
|
||||
vi.mock("../cli-credentials.js", () => ({
|
||||
readClaudeCliCredentialsCached: () => null,
|
||||
readCodexCliCredentialsCached: () => null,
|
||||
readMiniMaxCliCredentialsCached: () => null,
|
||||
resetCliCredentialCachesForTest: () => undefined,
|
||||
|
||||
@@ -19,6 +19,7 @@ vi.mock("@mariozechner/pi-ai/oauth", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("../cli-credentials.js", () => ({
|
||||
readClaudeCliCredentialsCached: () => null,
|
||||
readCodexCliCredentialsCached: () => null,
|
||||
readMiniMaxCliCredentialsCached: () => null,
|
||||
resetCliCredentialCachesForTest: () => undefined,
|
||||
|
||||
@@ -37,6 +37,7 @@ const {
|
||||
}));
|
||||
|
||||
vi.mock("../cli-credentials.js", () => ({
|
||||
readClaudeCliCredentialsCached: () => null,
|
||||
readCodexCliCredentialsCached: readCodexCliCredentialsCachedMock,
|
||||
readMiniMaxCliCredentialsCached: () => null,
|
||||
resetCliCredentialCachesForTest: () => undefined,
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { OpenClawConfig } from "../../config/config.js";
|
||||
import type { AuthProfileStore } from "./types.js";
|
||||
|
||||
vi.mock("../cli-credentials.js", () => ({
|
||||
readClaudeCliCredentialsCached: () => null,
|
||||
readCodexCliCredentialsCached: () => null,
|
||||
readMiniMaxCliCredentialsCached: () => null,
|
||||
resetCliCredentialCachesForTest: () => undefined,
|
||||
|
||||
@@ -23,6 +23,7 @@ vi.mock("./model-auth.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("./cli-credentials.js", () => ({
|
||||
readClaudeCliCredentialsCached: () => null,
|
||||
readCodexCliCredentialsCached: mocks.readCodexCliCredentialsCached,
|
||||
}));
|
||||
|
||||
|
||||
@@ -160,6 +160,7 @@ vi.mock("../plugins/providers.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("./cli-credentials.js", () => ({
|
||||
readClaudeCliCredentialsCached: () => null,
|
||||
readCodexCliCredentialsCached: () => null,
|
||||
readMiniMaxCliCredentialsCached: () => null,
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user