Skip to content

Commit 1e59f28

Browse files
authored
Merge 75fff82 into 045f42f
2 parents 045f42f + 75fff82 commit 1e59f28

3 files changed

Lines changed: 65 additions & 1 deletion

File tree

src/agents/auth-profiles/oauth-refresh-failure.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,22 @@ describe("oauth refresh failure hints", () => {
8585
reason: "token_invalidated",
8686
});
8787
});
88+
89+
it("recognizes claude-cli 401 subprocess output as an OAuth refresh failure", () => {
90+
// Claude CLI emits its own "Failed to authenticate. API Error: 401 ..." line
91+
// when its stored OAuth token expires. Without this match the channel reply
92+
// falls back to the generic "Something went wrong" text instead of the
93+
// re-auth hint.
94+
expect(
95+
classifyOAuthRefreshFailure(
96+
"Failed to authenticate. API Error: 401 Invalid authentication credentials",
97+
),
98+
).toEqual({
99+
provider: "claude-cli",
100+
reason: null,
101+
});
102+
expect(buildOAuthRefreshFailureLoginCommand("claude-cli")).toBe(
103+
"claude auth login && openclaw models auth login --provider anthropic --method cli --set-default",
104+
);
105+
});
88106
});

src/agents/auth-profiles/oauth-refresh-failure.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,23 @@ function isOAuthRefreshFailureMessage(message: string): boolean {
4545
return (
4646
lower.includes("oauth token refresh failed") ||
4747
lower.includes("access token could not be refreshed") ||
48-
lower.includes("authentication session could not be refreshed automatically")
48+
lower.includes("authentication session could not be refreshed automatically") ||
49+
isClaudeCliOAuth401Message(lower)
50+
);
51+
}
52+
53+
function isClaudeCliOAuth401Message(lowercaseMessage: string): boolean {
54+
return (
55+
lowercaseMessage.includes("failed to authenticate") &&
56+
lowercaseMessage.includes("api error") &&
57+
/\b401\b/u.test(lowercaseMessage)
4958
);
5059
}
5160

5261
function extractOAuthRefreshFailureProvider(message: string): string | null {
62+
if (isClaudeCliOAuth401Message(message.toLowerCase())) {
63+
return "claude-cli";
64+
}
5365
const provider = message.match(OAUTH_REFRESH_FAILURE_PROVIDER_RE)?.[1]?.trim();
5466
return provider && provider.length > 0 ? provider : null;
5567
}
@@ -142,6 +154,14 @@ export function buildOAuthRefreshFailureLoginCommand(
142154
options?: { profileId?: string | null },
143155
): string {
144156
const sanitizedProvider = sanitizeOAuthRefreshFailureProvider(provider);
157+
if (sanitizedProvider === "claude-cli") {
158+
return [
159+
"claude auth login",
160+
formatCliCommand(
161+
"openclaw models auth login --provider anthropic --method cli --set-default",
162+
),
163+
].join(" && ");
164+
}
145165
const sanitizedProfileId = sanitizeOAuthRefreshFailureProfileId(options?.profileId);
146166
return sanitizedProvider
147167
? formatCliCommand(

src/auto-reply/reply/agent-runner-execution.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7537,6 +7537,32 @@ describe("runAgentTurnWithFallback", () => {
75377537
}
75387538
});
75397539

7540+
it("surfaces Claude CLI OAuth 401 failures with claude-cli reauth guidance", async () => {
7541+
state.runEmbeddedAgentMock.mockRejectedValueOnce(
7542+
new FailoverError(
7543+
"Failed to authenticate. API Error: 401 Invalid authentication credentials",
7544+
{
7545+
reason: "auth",
7546+
provider: "claude-cli",
7547+
model: "claude-sonnet-4-6",
7548+
status: 401,
7549+
},
7550+
),
7551+
);
7552+
7553+
const runAgentTurnWithFallback = await getRunAgentTurnWithFallback();
7554+
const result = await runAgentTurnWithFallback(createMinimalRunAgentTurnParams());
7555+
7556+
expect(result.kind).toBe("final");
7557+
if (result.kind === "final") {
7558+
expect(result.payload.text).toBe(
7559+
"⚠️ Model login failed on the gateway for claude-cli. Please try again. If this keeps happening, re-auth with `claude auth login && openclaw models auth login --provider anthropic --method cli --set-default` in a terminal.",
7560+
);
7561+
expect(result.payload.text).not.toBe(PROVIDER_AUTHENTICATION_ERROR_USER_MESSAGE);
7562+
expect(result.payload.text).not.toBe(GENERIC_RUN_FAILURE_TEXT);
7563+
}
7564+
});
7565+
75407566
it("surfaces direct provider auth guidance for missing API keys", async () => {
75417567
state.runEmbeddedAgentMock.mockRejectedValueOnce(
75427568
new Error(

0 commit comments

Comments
 (0)