mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 04:47:03 -06:00
fix(doctor): ignore rotating Claude CLI OAuth expiry (#108205)
This commit is contained in:
committed by
GitHub
parent
f7718d9a91
commit
c6adbd233b
@@ -31,6 +31,9 @@ vi.mock("../agents/auth-profiles.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("../../packages/terminal-core/src/note.js", () => ({ note: vi.fn() }));
|
||||
vi.mock("../agents/auth-profiles/doctor.js", () => ({
|
||||
formatAuthDoctorHint: vi.fn(async () => "Re-authenticate this profile."),
|
||||
}));
|
||||
|
||||
import { note } from "../../packages/terminal-core/src/note.js";
|
||||
import { collectAuthProfileHealthFindings, noteAuthProfileHealth } from "./doctor-auth.js";
|
||||
@@ -110,6 +113,101 @@ describe("noteAuthProfileHealth", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not warn while Claude CLI owns refresh of an expiring access token", async () => {
|
||||
const now = 1_700_000_000_000;
|
||||
vi.spyOn(Date, "now").mockReturnValue(now);
|
||||
const mainDir = path.join(tempDir, "main-agent");
|
||||
authProfileMocks.hasAnyAuthProfileStoreSource.mockReturnValue(true);
|
||||
authProfileMocks.ensureAuthProfileStore.mockReturnValue({
|
||||
version: 1,
|
||||
profiles: {
|
||||
"anthropic:claude-cli": {
|
||||
type: "oauth",
|
||||
provider: "claude-cli",
|
||||
access: "access",
|
||||
refresh: "refresh",
|
||||
expires: now + 3 * 60 * 60_000,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const findings = await collectAuthProfileHealthFindings({
|
||||
cfg: {
|
||||
agents: { list: [{ id: "main", default: true, agentDir: mainDir }] },
|
||||
} as OpenClawConfig,
|
||||
});
|
||||
|
||||
expect(findings).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps expiring warnings for static and custom Claude CLI profiles", async () => {
|
||||
const now = 1_700_000_000_000;
|
||||
vi.spyOn(Date, "now").mockReturnValue(now);
|
||||
const mainDir = path.join(tempDir, "main-agent");
|
||||
authProfileMocks.hasAnyAuthProfileStoreSource.mockReturnValue(true);
|
||||
authProfileMocks.ensureAuthProfileStore.mockReturnValue({
|
||||
version: 1,
|
||||
profiles: {
|
||||
"anthropic:claude-cli": {
|
||||
type: "token",
|
||||
provider: "claude-cli",
|
||||
token: "token",
|
||||
expires: now + 3 * 60 * 60_000,
|
||||
},
|
||||
"anthropic:custom-cli": {
|
||||
type: "oauth",
|
||||
provider: "claude-cli",
|
||||
access: "access",
|
||||
refresh: "refresh",
|
||||
expires: now + 3 * 60 * 60_000,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const findings = await collectAuthProfileHealthFindings({
|
||||
cfg: {
|
||||
agents: { list: [{ id: "main", default: true, agentDir: mainDir }] },
|
||||
} as OpenClawConfig,
|
||||
});
|
||||
|
||||
expect(findings.map((finding) => finding.target)).toEqual([
|
||||
"anthropic:claude-cli",
|
||||
"anthropic:custom-cli",
|
||||
]);
|
||||
});
|
||||
|
||||
it("still warns once a Claude CLI access token is expired", async () => {
|
||||
const now = 1_700_000_000_000;
|
||||
vi.spyOn(Date, "now").mockReturnValue(now);
|
||||
const mainDir = path.join(tempDir, "main-agent");
|
||||
authProfileMocks.hasAnyAuthProfileStoreSource.mockReturnValue(true);
|
||||
authProfileMocks.ensureAuthProfileStore.mockReturnValue({
|
||||
version: 1,
|
||||
profiles: {
|
||||
"anthropic:claude-cli": {
|
||||
type: "oauth",
|
||||
provider: "claude-cli",
|
||||
access: "access",
|
||||
refresh: "refresh",
|
||||
expires: now - 60_000,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const findings = await collectAuthProfileHealthFindings({
|
||||
cfg: {
|
||||
agents: { list: [{ id: "main", default: true, agentDir: mainDir }] },
|
||||
} as OpenClawConfig,
|
||||
});
|
||||
|
||||
expect(findings).toEqual([
|
||||
expect.objectContaining({
|
||||
message: "Auth profile anthropic:claude-cli is expired (0m).",
|
||||
target: "anthropic:claude-cli",
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("maps disabled auth profiles to structured findings", async () => {
|
||||
const now = 1_700_000_000_000;
|
||||
vi.spyOn(Date, "now").mockReturnValue(now);
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
resolveApiKeyForProfile,
|
||||
resolveProfileUnusableUntilForDisplay,
|
||||
} from "../agents/auth-profiles.js";
|
||||
import { CLAUDE_CLI_PROFILE_ID } from "../agents/auth-profiles/constants.js";
|
||||
import { formatAuthDoctorHint } from "../agents/auth-profiles/doctor.js";
|
||||
import {
|
||||
buildOAuthRefreshFailureLoginCommand,
|
||||
@@ -38,6 +39,7 @@ import type { DoctorPrompter } from "./doctor-prompter.js";
|
||||
|
||||
const OPENAI_PROVIDER_ID = "openai";
|
||||
const LEGACY_CODEX_PROVIDER_ID = "openai-codex";
|
||||
const CLAUDE_CLI_PROVIDER_ID = "claude-cli";
|
||||
const CODEX_OAUTH_WARNING_TITLE = "Codex OAuth";
|
||||
const OPENAI_BASE_URL = "https://api.openai.com/v1";
|
||||
const LEGACY_CODEX_APIS = new Set(["openai-responses", "openai-completions"]);
|
||||
@@ -342,6 +344,16 @@ function isAuthProfileHealthIssue(profile: AuthHealthSummary["profiles"][number]
|
||||
if (profile.type === "api_key") {
|
||||
return profile.status === "missing";
|
||||
}
|
||||
// Claude CLI refreshes its short-lived access token when the process runs.
|
||||
// Warn once that external credential is unusable, not throughout its normal lifetime.
|
||||
if (
|
||||
profile.profileId === CLAUDE_CLI_PROFILE_ID &&
|
||||
profile.provider === CLAUDE_CLI_PROVIDER_ID &&
|
||||
profile.type === "oauth" &&
|
||||
profile.status === "expiring"
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
(profile.type === "oauth" || profile.type === "token") &&
|
||||
(profile.status === "expired" || profile.status === "expiring" || profile.status === "missing")
|
||||
|
||||
Reference in New Issue
Block a user