fix(cron): require HTTP context for server_error retries

Narrow cron server_error retry classification so incidental 500-599 numbers in failure text no longer trigger retryable server_error. Genuine HTTP/status 5xx strings, canonical 5xx phrases, 5xx, and standalone terse codes still retry.

Maintainer proof: focused cron retry tests, formatter/lint/diff checks, clean autoreview, Testbox-through-Crabbox check:changed tbx_01kteteqqrppbzgh560sybe0nk / Actions run 27066938422, and green PR CI on 6124f14850.

Fixes #90947.
This commit is contained in:
nas
2026-06-06 12:06:31 -04:00
committed by GitHub
parent e099c01a8c
commit 63fba5d2fe
2 changed files with 46 additions and 1 deletions
+34
View File
@@ -36,4 +36,38 @@ describe("resolveCronExecutionRetryHint", () => {
retryable: false,
});
});
it("does not classify bare 5xx-looking numbers as server_error", () => {
for (const message of [
"context limit 512 exceeded",
"process exited with 503 lines of output",
"ENOENT: no such file '/var/run/app-540.sock'",
"killed worker pid 511 after deadline",
"assertion failed: expected 500 got 0",
"error 500 got 0",
"process exited with code 500",
]) {
expect(resolveCronExecutionRetryHint(message, ["server_error"])).toEqual({
retryable: false,
});
}
});
it("classifies genuine HTTP 5xx errors as server_error", () => {
for (const message of [
"HTTP 503 Service Unavailable",
"received status 500 from upstream",
"500 Internal Server Error",
"502 Bad Gateway",
"upstream returned 5xx",
"response code: 502",
"503",
"500",
]) {
expect(resolveCronExecutionRetryHint(message, ["server_error"])).toEqual({
retryable: true,
category: "server_error",
});
}
});
});
+12 -1
View File
@@ -7,6 +7,17 @@ export type CronRetryHint = {
category?: CronRetryOn;
};
// A bare 5xx-looking number embedded in prose is not an HTTP server error: cron
// failure messages routinely contain such numbers ("context limit 512 exceeded",
// "exited with 503 lines", "pid 511 killed", a "...-540.sock" path), and
// /\b5\d{2}\b/ matched all of them, wrongly marking permanent failures retryable.
// Match a 5xx number only with HTTP/status context, a canonical 5xx phrase, or
// when it is the entire message (a terse "503"), so genuine
// "500 Internal Server Error" / "502 Bad Gateway" / "5xx" still classify while
// incidental numbers in longer messages do not.
const SERVER_ERROR_PATTERN =
/\b(?:https?|status(?:[ _]code)?|response(?:[ _]code)?|http(?:[ _]status)?)\b[\s:=#"']{0,4}5\d{2}\b|\b5\d{2}\b[\s:)\].,-]*(?:internal server error|server error|bad gateway|service unavailable|gateway time-?out)\b|\binternal server error\b|\bbad gateway\b|\bservice unavailable\b|\bgateway time-?out\b|\b5xx\b|^\s*5\d{2}\s*$/i;
const TRANSIENT_PATTERNS: Record<CronRetryOn, RegExp> = {
rate_limit:
/(rate[_ ]limit|too many requests|429|resource has been exhausted|cloudflare|tokens per day)/i,
@@ -15,7 +26,7 @@ const TRANSIENT_PATTERNS: Record<CronRetryOn, RegExp> = {
network:
/(network|fetch failed|socket|econnreset|econnrefused|eai_again|enetdown|ehostunreach|ehostdown|enetreset|enetunreach|epipe)/i,
timeout: /(timeout|etimedout)/i,
server_error: /\b5\d{2}\b/,
server_error: SERVER_ERROR_PATTERN,
};
/** Classifies cron execution errors against the configured retryable transient categories. */