Skip to content

Commit a65d70f

Browse files
Fix failover for zhipuai 1310 Weekly/Monthly Limit Exhausted (openclaw#33813)
Merged via squash. Prepared head SHA: 3dc441e Co-authored-by: zhouhe-xydt <[email protected]> Co-authored-by: altaywtf <[email protected]> Reviewed-by: @altaywtf
1 parent ee6f7b1 commit a65d70f

File tree

5 files changed

+56
-0
lines changed

5 files changed

+56
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ Docs: https://docs.openclaw.ai
194194
- Memory/flush default prompt: ban timestamped variant filenames during default memory flush runs so durable notes stay in the canonical daily `memory/YYYY-MM-DD.md` file. (#34951) thanks @zerone0x.
195195
- Agents/reply delivery timing: flush embedded Pi block replies before waiting on compaction retries so already-generated assistant replies reach channels before compaction wait completes. (#35489) thanks @Sid-Qin.
196196
- Agents/gateway config guidance: stop exposing `config.schema` through the agent `gateway` tool, remove prompt/docs guidance that told agents to call it, and keep agents on `config.get` plus `config.patch`/`config.apply` for config changes. (#7382) thanks @kakuteki.
197+
- Agents/failover: classify periodic provider limit exhaustion text (for example `Weekly/Monthly Limit Exhausted`) as `rate_limit` while keeping explicit `402 Payment Required` variants in billing, so failover continues without misclassifying billing-wrapped quota errors. (#33813) thanks @zhouhe-xydt.
197198

198199
## 2026.3.2
199200

src/agents/failover-error.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ const OPENROUTER_CREDITS_MESSAGE = "Payment Required: insufficient credits";
2222
// https://github.com/openclaw/openclaw/issues/23440
2323
const INSUFFICIENT_QUOTA_PAYLOAD =
2424
'{"type":"error","error":{"type":"insufficient_quota","message":"Your account has insufficient quota balance to run this request."}}';
25+
// Issue-backed ZhipuAI/GLM quota-exhausted log from #33785:
26+
// https://github.com/openclaw/openclaw/issues/33785
27+
const ZHIPUAI_WEEKLY_MONTHLY_LIMIT_EXHAUSTED_MESSAGE =
28+
"LLM error 1310: Weekly/Monthly Limit Exhausted. Your limit will reset at 2026-03-06 22:19:54 (request_id: 20260303141547610b7f574d1b44cb)";
2529
// AWS Bedrock 429 ThrottlingException / 503 ServiceUnavailable:
2630
// https://docs.aws.amazon.com/bedrock/latest/userguide/troubleshooting-api-error-codes.html
2731
const BEDROCK_THROTTLING_EXCEPTION_MESSAGE =
@@ -113,6 +117,27 @@ describe("failover-error", () => {
113117
).toBe("billing");
114118
});
115119

120+
it("treats zhipuai weekly/monthly limit exhausted as rate_limit", () => {
121+
expect(
122+
resolveFailoverReasonFromError({
123+
message: ZHIPUAI_WEEKLY_MONTHLY_LIMIT_EXHAUSTED_MESSAGE,
124+
}),
125+
).toBe("rate_limit");
126+
expect(
127+
resolveFailoverReasonFromError({
128+
message: "LLM error: monthly limit reached",
129+
}),
130+
).toBe("rate_limit");
131+
});
132+
133+
it("keeps raw-text 402 weekly/monthly limit errors in billing", () => {
134+
expect(
135+
resolveFailoverReasonFromError({
136+
message: "402 Payment Required: Weekly/Monthly Limit Exhausted",
137+
}),
138+
).toBe("billing");
139+
});
140+
116141
it("infers format errors from error messages", () => {
117142
expect(
118143
resolveFailoverReasonFromError({

src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,14 @@ describe("classifyFailoverReason", () => {
535535
).toBe("rate_limit");
536536
expect(classifyFailoverReason("all credentials for model x are cooling down")).toBeNull();
537537
expect(classifyFailoverReason("invalid request format")).toBe("format");
538+
expect(classifyFailoverReason("credit balance too low")).toBe("billing");
539+
// Billing with "limit exhausted" must stay billing, not rate_limit (avoids key-disable regression)
540+
expect(
541+
classifyFailoverReason("HTTP 402 payment required. Your limit exhausted for this plan."),
542+
).toBe("billing");
543+
expect(classifyFailoverReason("402 Payment Required: Weekly/Monthly Limit Exhausted")).toBe(
544+
"billing",
545+
);
538546
expect(classifyFailoverReason(INSUFFICIENT_QUOTA_PAYLOAD)).toBe("billing");
539547
expect(classifyFailoverReason("deadline exceeded")).toBe("timeout");
540548
expect(classifyFailoverReason("request ended without sending any chunks")).toBe("timeout");
@@ -584,6 +592,17 @@ describe("classifyFailoverReason", () => {
584592
// but it should not be treated as provider overload / rate limit.
585593
expect(classifyFailoverReason("LLM error: service unavailable")).toBe("timeout");
586594
});
595+
it("classifies zhipuai Weekly/Monthly Limit Exhausted as rate_limit (#33785)", () => {
596+
expect(
597+
classifyFailoverReason(
598+
"LLM error 1310: Weekly/Monthly Limit Exhausted. Your limit will reset at 2026-03-06 22:19:54 (request_id: 20260303141547610b7f574d1b44cb)",
599+
),
600+
).toBe("rate_limit");
601+
// Independent coverage for broader periodic limit patterns.
602+
expect(classifyFailoverReason("LLM error: weekly/monthly limit reached")).toBe("rate_limit");
603+
expect(classifyFailoverReason("LLM error: monthly limit reached")).toBe("rate_limit");
604+
expect(classifyFailoverReason("LLM error: daily limit exceeded")).toBe("rate_limit");
605+
});
587606
it("classifies permanent auth errors as auth_permanent", () => {
588607
expect(classifyFailoverReason("invalid_api_key")).toBe("auth_permanent");
589608
expect(classifyFailoverReason("Your api key has been revoked")).toBe("auth_permanent");

src/agents/pi-embedded-helpers/errors.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
isAuthPermanentErrorMessage,
99
isBillingErrorMessage,
1010
isOverloadedErrorMessage,
11+
isPeriodicUsageLimitErrorMessage,
1112
isRateLimitErrorMessage,
1213
isTimeoutErrorMessage,
1314
matchesFormatErrorPattern,
@@ -842,6 +843,9 @@ export function classifyFailoverReason(raw: string): FailoverReason | null {
842843
if (isJsonApiInternalServerError(raw)) {
843844
return "timeout";
844845
}
846+
if (isPeriodicUsageLimitErrorMessage(raw)) {
847+
return isBillingErrorMessage(raw) ? "billing" : "rate_limit";
848+
}
845849
if (isRateLimitErrorMessage(raw)) {
846850
return "rate_limit";
847851
}

src/agents/pi-embedded-helpers/failover-matches.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
type ErrorPattern = RegExp | string;
22

3+
const PERIODIC_USAGE_LIMIT_RE =
4+
/\b(?:daily|weekly|monthly)(?:\/(?:daily|weekly|monthly))* (?:usage )?limit(?:s)?(?: (?:exhausted|reached|exceeded))?\b/i;
5+
36
const ERROR_PATTERNS = {
47
rateLimit: [
58
/rate[_ ]limit|too many requests|429/,
@@ -117,6 +120,10 @@ export function isTimeoutErrorMessage(raw: string): boolean {
117120
return matchesErrorPatterns(raw, ERROR_PATTERNS.timeout);
118121
}
119122

123+
export function isPeriodicUsageLimitErrorMessage(raw: string): boolean {
124+
return PERIODIC_USAGE_LIMIT_RE.test(raw);
125+
}
126+
120127
export function isBillingErrorMessage(raw: string): boolean {
121128
const value = raw.toLowerCase();
122129
if (!value) {

0 commit comments

Comments
 (0)