Skip to content

Commit 755cff8

Browse files
committed
fix(errors): guard HTML rate limit copy
1 parent e97efdd commit 755cff8

File tree

3 files changed

+33
-3
lines changed

3 files changed

+33
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Docs: https://docs.openclaw.ai
4242
- WhatsApp: fix infinite echo loop in self-chat DM mode where the bot's own outbound replies were re-processed as new inbound user messages. (#54570) Thanks @joelnishanth
4343
- Discord/reconnect: drain stale gateway sockets, clear cached resume state before forced fresh reconnects, and fail closed when old sockets refuse to die so Discord recovery stops looping on poisoned resume state. (#54697) Thanks @ngutman.
4444
- BlueBubbles/groups: optionally enrich unnamed participant lists with local macOS Contacts names after group gating passes, so group member context can show names instead of only raw phone numbers.
45+
- Agents/errors: surface provider quota/reset details when available, but keep HTML/Cloudflare rate-limit pages on the generic fallback so raw error pages are not shown to users. (#54512) Thanks @bugkill3r.
4546

4647
## 2026.3.24
4748

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,24 @@ describe("formatAssistantErrorText", () => {
155155
expect(result).toBe("⚠️ Your quota has been exhausted, try again in 24 hours");
156156
});
157157

158+
it("falls back to generic copy for HTML quota pages", () => {
159+
const msg = makeAssistantError(
160+
"429 <!DOCTYPE html><html><body>Your quota is exhausted</body></html>",
161+
);
162+
expect(formatAssistantErrorText(msg)).toBe(
163+
"⚠️ API rate limit reached. Please try again later.",
164+
);
165+
});
166+
167+
it("falls back to generic copy for prefixed HTML rate-limit pages", () => {
168+
const msg = makeAssistantError(
169+
"Error: 521 <!DOCTYPE html><html><body>rate limit</body></html>",
170+
);
171+
expect(formatAssistantErrorText(msg)).toBe(
172+
"⚠️ API rate limit reached. Please try again later.",
173+
);
174+
});
175+
158176
it("returns a friendly message for empty stream chunk errors", () => {
159177
const msg = makeAssistantError("request ended without sending any chunks");
160178
expect(formatAssistantErrorText(msg)).toBe("LLM request timed out.");

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,19 +66,30 @@ const RATE_LIMIT_SPECIFIC_HINT_RE =
6666
/\bmin(ute)?s?\b|\bhours?\b|\bseconds?\b|\btry again in\b|\breset\b|\bplan\b|\bquota\b/i;
6767

6868
function extractProviderRateLimitMessage(raw: string): string | undefined {
69+
const withoutPrefix = raw.replace(ERROR_PREFIX_RE, "").trim();
6970
// Try to pull a human-readable message out of a JSON error payload first.
70-
const info = parseApiErrorInfo(raw);
71+
const info = parseApiErrorInfo(raw) ?? parseApiErrorInfo(withoutPrefix);
7172
// When the raw string is not a JSON payload, strip any leading HTTP status
7273
// code (e.g. "429 ") so the surfaced message stays clean.
73-
const candidate = info?.message ?? (extractLeadingHttpStatus(raw.trim())?.rest || raw);
74+
const candidate =
75+
info?.message ?? (extractLeadingHttpStatus(withoutPrefix)?.rest || withoutPrefix);
7476

7577
if (!candidate || !RATE_LIMIT_SPECIFIC_HINT_RE.test(candidate)) {
7678
return undefined;
7779
}
7880

81+
// Skip HTML/Cloudflare error pages even if the body mentions quota/plan text.
82+
if (isCloudflareOrHtmlErrorPage(withoutPrefix)) {
83+
return undefined;
84+
}
85+
7986
// Avoid surfacing very long or clearly non-human-readable blobs.
8087
const trimmed = candidate.trim();
81-
if (trimmed.length > 300 || trimmed.startsWith("{")) {
88+
if (
89+
trimmed.length > 300 ||
90+
trimmed.startsWith("{") ||
91+
/^(?:<!doctype\s+html\b|<html\b)/i.test(trimmed)
92+
) {
8293
return undefined;
8394
}
8495

0 commit comments

Comments
 (0)