+ Just a moment...
+
+
+
+ Enable JavaScript and cookies to continue
+
+
+`);
+ expect(formatAssistantErrorText(msg)).toBe(
+ "The provider returned an HTML error page instead of an API response. This usually means a CDN or gateway (e.g. Cloudflare) blocked the request. Retry in a moment or check provider status.",
+ );
+ });
+
it("returns a friendly message for empty stream chunk errors", () => {
const msg = makeAssistantError("request ended without sending any chunks");
expect(formatAssistantErrorText(msg)).toBe("LLM request timed out.");
@@ -339,6 +356,21 @@ describe("formatRawAssistantErrorForUi", () => {
"The AI service is temporarily unavailable (HTTP 521). Please try again in a moment.",
);
});
+
+ it("formats standalone Cloudflare challenge HTML into a clean provider error", () => {
+ const htmlError = `
+
+ Just a moment...
+
+ Enable JavaScript and cookies to continue
+
+
+`;
+
+ expect(formatRawAssistantErrorForUi(htmlError)).toBe(
+ "The provider returned an HTML error page instead of an API response. This usually means a CDN or gateway (e.g. Cloudflare) blocked the request. Retry in a moment or check provider status.",
+ );
+ });
});
describe("raw API error payload helpers", () => {
diff --git a/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts b/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts
index 1ac3fd0a26a..e0999a1d9bb 100644
--- a/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts
+++ b/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts
@@ -266,6 +266,18 @@ describe("isCloudflareOrHtmlErrorPage", () => {
expect(isCloudflareOrHtmlErrorPage(htmlError)).toBe(true);
});
+ it("detects standalone Cloudflare challenge HTML pages", () => {
+ const htmlError = `
+
+ Just a moment...
+
+ Enable JavaScript and cookies to continue
+
+
+`;
+ expect(isCloudflareOrHtmlErrorPage(htmlError)).toBe(true);
+ });
+
it("does not flag non-HTML status lines", () => {
expect(isCloudflareOrHtmlErrorPage("500 Internal Server Error")).toBe(false);
expect(isCloudflareOrHtmlErrorPage("429 Too Many Requests")).toBe(false);
diff --git a/src/agents/pi-embedded-helpers/sanitize-user-facing-text.ts b/src/agents/pi-embedded-helpers/sanitize-user-facing-text.ts
index 70ef2eb57f3..f0e3ace5332 100644
--- a/src/agents/pi-embedded-helpers/sanitize-user-facing-text.ts
+++ b/src/agents/pi-embedded-helpers/sanitize-user-facing-text.ts
@@ -102,6 +102,11 @@ export function formatTransportErrorCopy(raw: string): string | undefined {
if (!raw) {
return undefined;
}
+
+ if (isCloudflareOrHtmlErrorPage(raw)) {
+ return undefined;
+ }
+
const lower = normalizeLowercaseStringOrEmpty(raw);
if (
diff --git a/src/shared/assistant-error-format.ts b/src/shared/assistant-error-format.ts
index 80598de0244..7f00a05a905 100644
--- a/src/shared/assistant-error-format.ts
+++ b/src/shared/assistant-error-format.ts
@@ -10,7 +10,10 @@ const HTTP_STATUS_CODE_PREFIX_RE = new RegExp(
"i",
);
const HTML_ERROR_PREFIX_RE = /^\s*(?:/i;
const CLOUDFLARE_HTML_ERROR_CODES = new Set([521, 522, 523, 524, 525, 526, 530]);
+const STANDALONE_HTML_ERROR_HINT_RE =
+ /\bcloudflare\b|cdn-cgi\/challenge-platform|challenge-error-text|enable javascript and cookies to continue|access denied|forbidden|service unavailable|bad gateway|web server is down|captcha|attention required/i;
type ErrorPayload = Record;
@@ -94,6 +97,14 @@ export function isCloudflareOrHtmlErrorPage(raw: string): boolean {
return false;
}
+ if (
+ HTML_ERROR_PREFIX_RE.test(trimmed) &&
+ HTML_CLOSE_RE.test(trimmed) &&
+ STANDALONE_HTML_ERROR_HINT_RE.test(trimmed)
+ ) {
+ return true;
+ }
+
const status = extractLeadingHttpStatus(trimmed);
if (!status || status.code < 500) {
return false;
@@ -104,7 +115,7 @@ export function isCloudflareOrHtmlErrorPage(raw: string): boolean {
}
return (
- status.code < 600 && HTML_ERROR_PREFIX_RE.test(status.rest) && /<\/html>/i.test(status.rest)
+ status.code < 600 && HTML_ERROR_PREFIX_RE.test(status.rest) && HTML_CLOSE_RE.test(status.rest)
);
}
@@ -175,6 +186,14 @@ export function formatRawAssistantErrorForUi(raw?: string): string {
return `The AI service is temporarily unavailable (HTTP ${leadingStatus.code}). Please try again in a moment.`;
}
+ if (isCloudflareOrHtmlErrorPage(trimmed)) {
+ return (
+ "The provider returned an HTML error page instead of an API response. " +
+ "This usually means a CDN or gateway (e.g. Cloudflare) blocked the request. " +
+ "Retry in a moment or check provider status."
+ );
+ }
+
const httpMatch = trimmed.match(HTTP_STATUS_PREFIX_RE);
if (httpMatch) {
const rest = httpMatch[2].trim();