惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Last Week in AI
Last Week in AI
D
DataBreaches.Net
腾讯CDC
Recent Announcements
Recent Announcements
有赞技术团队
有赞技术团队
A
About on SuperTechFans
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Google DeepMind News
Google DeepMind News
Microsoft Security Blog
Microsoft Security Blog
云风的 BLOG
云风的 BLOG
罗磊的独立博客
月光博客
月光博客
MyScale Blog
MyScale Blog
U
Unit 42
Martin Fowler
Martin Fowler
Stack Overflow Blog
Stack Overflow Blog
T
Tailwind CSS Blog
Engineering at Meta
Engineering at Meta
N
Netflix TechBlog - Medium
G
Google Developers Blog
博客园 - 【当耐特】
D
Docker
I
InfoQ
雷峰网
雷峰网

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix: surface provider authentication failures in channels...
sjf-oa · 2026-06-25 · via Recent Commits to openclaw:main
Original file line numberDiff line numberDiff line change

@@ -75,6 +75,10 @@ export {

7575

const log = createSubsystemLogger("errors");

7676

const sandboxToolPolicyAuditMessages = new WeakSet<AssistantMessage>();

7777

export const GENERIC_ASSISTANT_ERROR_TEXT = "LLM request failed.";

78+

export const AUTH_INVALID_TOKEN_USER_TEXT =

79+

"Authentication failed (provider returned HTTP 401). " +

80+

"Your provider token may have expired — try the request again in a moment. " +

81+

"If the failure persists, re-authenticate this provider.";

7882

const PROVIDER_SCHEMA_REJECTION_USER_TEXT =

7983

"LLM request failed: provider rejected the request schema or tool payload.";

8084

const MODEL_NOT_FOUND_USER_TEXT =

@@ -1419,11 +1423,7 @@ export function formatAssistantErrorText(

14191423

}

14201424
14211425

if (providerRuntimeFailureKind === "auth_invalid_token") {

1422-

return (

1423-

"Authentication failed (provider returned HTTP 401). " +

1424-

"Your provider token may have expired — try the request again in a moment. " +

1425-

"If the failure persists, re-authenticate this provider."

1426-

);

1426+

return AUTH_INVALID_TOKEN_USER_TEXT;

14271427

}

14281428
14291429

if (providerRuntimeFailureKind === "upstream_html") {

Original file line numberDiff line numberDiff line change

@@ -28,6 +28,7 @@ import {

2828

} from "./agent-runner-execution.js";

2929

import { HEARTBEAT_EXTERNAL_RUN_FAILURE_TEXT } from "./agent-runner-failure-copy.js";

3030

import {

31+

PROVIDER_AUTHENTICATION_ERROR_USER_MESSAGE,

3132

PROVIDER_CONVERSATION_STATE_ERROR_USER_MESSAGE,

3233

PROVIDER_INTERNAL_ERROR_USER_MESSAGE,

3334

PROVIDER_RATE_LIMIT_OR_QUOTA_ERROR_USER_MESSAGE,

@@ -6534,6 +6535,38 @@ describe("runAgentTurnWithFallback", () => {

65346535

},

65356536

);

65366537
6538+

it.each(NON_DIRECT_FAILURE_SURFACE_CASES)(

6539+

"surfaces provider authentication failures in $label chats",

6540+

async (testCase) => {

6541+

const rawError =

6542+

"unexpected status 401 Unauthorized: Missing bearer or basic authentication in header, url: https://api.openai.com/v1/responses";

6543+

state.runEmbeddedAgentMock.mockRejectedValueOnce(

6544+

new FailoverError("LLM request unauthorized.", {

6545+

reason: "auth",

6546+

provider: "openai",

6547+

model: "gpt-5.5",

6548+

status: 401,

6549+

rawError,

6550+

}),

6551+

);

6552+
6553+

const runAgentTurnWithFallback = await getRunAgentTurnWithFallback();

6554+

const result = await runAgentTurnWithFallback(

6555+

createMinimalRunAgentTurnParams({

6556+

sessionCtx: createNonDirectFailureSessionCtx(testCase),

6557+

}),

6558+

);

6559+
6560+

expect(result.kind).toBe("final");

6561+

if (result.kind === "final") {

6562+

expect(result.payload.isError).toBe(true);

6563+

expect(result.payload.text).toBe(PROVIDER_AUTHENTICATION_ERROR_USER_MESSAGE);

6564+

expect(result.payload.text).not.toBe(SILENT_REPLY_TOKEN);

6565+

expect(result.payload.text).not.toContain(rawError);

6566+

}

6567+

},

6568+

);

6569+
65376570

it.each(NON_DIRECT_FAILURE_SURFACE_CASES)(

65386571

"surfaces rate-limit fallback copy in $label chats",

65396572

async (testCase) => {

Original file line numberDiff line numberDiff line change

@@ -1,13 +1,60 @@

11

/** Tests provider request error classification for retry/fallback decisions. */

22

import { describe, expect, it } from "vitest";

3+

import { FailoverError } from "../../agents/failover-error.js";

34

import {

45

classifyProviderRequestError,

6+

PROVIDER_AUTHENTICATION_ERROR_USER_MESSAGE,

57

PROVIDER_CONVERSATION_STATE_ERROR_USER_MESSAGE,

68

PROVIDER_INTERNAL_ERROR_USER_MESSAGE,

79

PROVIDER_RATE_LIMIT_OR_QUOTA_ERROR_USER_MESSAGE,

810

} from "./provider-request-error-classifier.js";

911
1012

describe("provider request error classifier", () => {

13+

it("classifies provider HTTP 401 authentication failures", () => {

14+

const message =

15+

"unexpected status 401 Unauthorized: Missing bearer or basic authentication in header, url: https://api.openai.com/v1/responses";

16+
17+

expect(classifyProviderRequestError(new Error(message))).toEqual({

18+

code: "provider_authentication_error",

19+

userMessage: PROVIDER_AUTHENTICATION_ERROR_USER_MESSAGE,

20+

technicalMessage: message,

21+

});

22+

});

23+
24+

it("classifies typed authentication failures without relying on raw provider text", () => {

25+

const error = new FailoverError("LLM request unauthorized.", {

26+

reason: "auth",

27+

provider: "openai",

28+

model: "gpt-5.5",

29+

status: 401,

30+

});

31+
32+

expect(classifyProviderRequestError(error)).toEqual({

33+

code: "provider_authentication_error",

34+

userMessage: PROVIDER_AUTHENTICATION_ERROR_USER_MESSAGE,

35+

technicalMessage: "LLM request unauthorized.",

36+

});

37+

});

38+
39+

it("does not label typed HTTP 403 authorization failures as HTTP 401", () => {

40+

const error = new FailoverError("Provider access denied.", {

41+

reason: "auth_permanent",

42+

provider: "openai",

43+

model: "gpt-5.5",

44+

status: 403,

45+

});

46+
47+

expect(classifyProviderRequestError(error)).toBeUndefined();

48+

});

49+
50+

it("leaves unrelated HTTP 401 failures unclassified", () => {

51+

expect(

52+

classifyProviderRequestError(

53+

new Error("401 input item id does not belong to this conversation"),

54+

),

55+

).toBeUndefined();

56+

});

57+
1158

it.each([

1259

[

1360

"OpenAI missing custom tool output",

Original file line numberDiff line numberDiff line change

@@ -1,9 +1,15 @@

11

// Classifies provider request failures into retry and user-facing categories.

22

import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce";

3+

import {

4+

AUTH_INVALID_TOKEN_USER_TEXT,

5+

classifyProviderRuntimeFailureKind,

6+

} from "../../agents/embedded-agent-helpers/errors.js";

7+

import { isFailoverError } from "../../agents/failover-error.js";

38

import { formatErrorMessage } from "../../infra/errors.js";

49
510

/** Provider request error classes that get a specialized user-facing reply. */

611

export type ProviderRequestErrorCode =

12+

| "provider_authentication_error"

713

| "provider_conversation_state_error"

814

| "provider_internal_error"

915

| "provider_rate_limit_or_quota_error";

@@ -25,11 +31,24 @@ export const PROVIDER_RATE_LIMIT_OR_QUOTA_ERROR_USER_MESSAGE =

2531

export const PROVIDER_INTERNAL_ERROR_USER_MESSAGE =

2632

"⚠️ The model provider returned a temporary internal error before replying. Try again in a moment, or switch to another model if it keeps happening.";

2733
34+

export const PROVIDER_AUTHENTICATION_ERROR_USER_MESSAGE = `⚠️ ${AUTH_INVALID_TOKEN_USER_TEXT}`;

35+
2836

/** Classifies provider request failures that are actionable for users. */

2937

export function classifyProviderRequestError(

3038

err: unknown,

3139

): ProviderRequestErrorClassification | undefined {

3240

const technicalMessage = formatErrorMessage(err);

41+

const isTypedAuthFailure = isFailoverError(err) && err.reason === "auth" && err.status === 401;

42+

if (

43+

isTypedAuthFailure ||

44+

classifyProviderRuntimeFailureKind(technicalMessage) === "auth_invalid_token"

45+

) {

46+

return {

47+

code: "provider_authentication_error",

48+

userMessage: PROVIDER_AUTHENTICATION_ERROR_USER_MESSAGE,

49+

technicalMessage,

50+

};

51+

}

3352

if (

3453

hasHttp429Evidence(err, technicalMessage) &&

3554

isGenericProviderRuntimeErrorMessage(technicalMessage)