fix: cover sub2api model routing 503 cooldown

This commit is contained in:
Codex
2026-06-09 12:49:46 +00:00
parent 6cde878da6
commit 95b94ae1bd
4 changed files with 15 additions and 4 deletions
@@ -31,9 +31,9 @@ pool:
durationMinutes: 30 durationMinutes: 30
description: Gateway upstream failures, including recovered upstream error wrappers, should cool down longer. description: Gateway upstream failures, including recovered upstream error wrappers, should cool down longer.
- statusCode: 503 - statusCode: 503
keywords: [capacity, overloaded, temporarily unavailable, temporary, upstream, recovered upstream error] keywords: [capacity, overloaded, temporarily unavailable, temporary, upstream, recovered upstream error, model_not_found, no available channel for model]
durationMinutes: 30 durationMinutes: 30
description: Service unavailable responses should cool down longer than one-off transient failures. description: Service unavailable and upstream model-routing failures should cool down longer than one-off transient failures.
- statusCode: 529 - statusCode: 529
keywords: [capacity, overloaded, temporarily unavailable, temporary, recovered upstream error] keywords: [capacity, overloaded, temporarily unavailable, temporary, recovered upstream error]
durationMinutes: 30 durationMinutes: 30
@@ -50,12 +50,17 @@ if (parsed.pool?.defaultTempUnschedulable?.enabled === true) {
assertCondition(gateway502Keywords.has("recovered upstream error"), "502 temporary-unschedulable rule must catch recovered upstream error wrappers", gateway502Rule); assertCondition(gateway502Keywords.has("recovered upstream error"), "502 temporary-unschedulable rule must catch recovered upstream error wrappers", gateway502Rule);
const accountState403Rule = rules.find((rule) => rule.statusCode === 403); const accountState403Rule = rules.find((rule) => rule.statusCode === 403);
const quota429Rule = rules.find((rule) => rule.statusCode === 429); const quota429Rule = rules.find((rule) => rule.statusCode === 429);
const serviceUnavailable503Rule = rules.find((rule) => rule.statusCode === 503);
const accountState403Keywords = new Set((accountState403Rule?.keywords ?? []).map((keyword) => keyword.toLowerCase())); const accountState403Keywords = new Set((accountState403Rule?.keywords ?? []).map((keyword) => keyword.toLowerCase()));
const quota429Keywords = new Set((quota429Rule?.keywords ?? []).map((keyword) => keyword.toLowerCase())); const quota429Keywords = new Set((quota429Rule?.keywords ?? []).map((keyword) => keyword.toLowerCase()));
const serviceUnavailable503Keywords = new Set((serviceUnavailable503Rule?.keywords ?? []).map((keyword) => keyword.toLowerCase()));
for (const keyword of ["weekly limit", "less than 10% of your weekly limit left", "run /status for a breakdown"]) { for (const keyword of ["weekly limit", "less than 10% of your weekly limit left", "run /status for a breakdown"]) {
assertCondition(accountState403Keywords.has(keyword), "403 temporary-unschedulable rule must catch Codex weekly-limit account-state prompts", { keyword, accountState403Rule }); assertCondition(accountState403Keywords.has(keyword), "403 temporary-unschedulable rule must catch Codex weekly-limit account-state prompts", { keyword, accountState403Rule });
assertCondition(quota429Keywords.has(keyword), "429 temporary-unschedulable rule must catch Codex weekly-limit quota prompts", { keyword, quota429Rule }); assertCondition(quota429Keywords.has(keyword), "429 temporary-unschedulable rule must catch Codex weekly-limit quota prompts", { keyword, quota429Rule });
} }
for (const keyword of ["model_not_found", "no available channel for model"]) {
assertCondition(serviceUnavailable503Keywords.has(keyword), "503 temporary-unschedulable rule must catch upstream model-routing failures", { keyword, serviceUnavailable503Rule });
}
} }
assertCondition(typeof parsed.localCodex?.responsesSmokeModel === "string" && parsed.localCodex.responsesSmokeModel.length > 0, "localCodex.responsesSmokeModel must be declared for Responses smoke validation", parsed.localCodex); assertCondition(typeof parsed.localCodex?.responsesSmokeModel === "string" && parsed.localCodex.responsesSmokeModel.length > 0, "localCodex.responsesSmokeModel must be declared for Responses smoke validation", parsed.localCodex);
@@ -69,6 +74,7 @@ console.log(JSON.stringify({
"temporary unschedulable rules are structurally valid when enabled", "temporary unschedulable rules are structurally valid when enabled",
"generic recovered upstream error wrappers are caught by cooldown rules", "generic recovered upstream error wrappers are caught by cooldown rules",
"Codex weekly-limit prompts are caught by account-state and quota cooldown rules", "Codex weekly-limit prompts are caught by account-state and quota cooldown rules",
"upstream model-routing failures are caught by the 503 cooldown rule",
"Responses smoke model is YAML-declared", "Responses smoke model is YAML-declared",
], ],
})); }));
@@ -29,10 +29,14 @@ assertCondition(!("pool_mode" in credentials), "pool_mode must not be enabled be
assertCondition(!("api_key" in credentials) && !("base_url" in credentials), "temporary-unschedulable rendering must not include secrets or endpoints", credentials); assertCondition(!("api_key" in credentials) && !("base_url" in credentials), "temporary-unschedulable rendering must not include secrets or endpoints", credentials);
const accountState403Rule = rules.find((rule) => rule.error_code === 403); const accountState403Rule = rules.find((rule) => rule.error_code === 403);
const quota429Rule = rules.find((rule) => rule.error_code === 429); const quota429Rule = rules.find((rule) => rule.error_code === 429);
const serviceUnavailable503Rule = rules.find((rule) => rule.error_code === 503);
for (const keyword of ["weekly limit", "less than 10% of your weekly limit left", "run /status for a breakdown"]) { for (const keyword of ["weekly limit", "less than 10% of your weekly limit left", "run /status for a breakdown"]) {
assertCondition(accountState403Rule?.keywords?.includes(keyword), "403 rendered rule must preserve Codex weekly-limit account-state keyword", { keyword, accountState403Rule }); assertCondition(accountState403Rule?.keywords?.includes(keyword), "403 rendered rule must preserve Codex weekly-limit account-state keyword", { keyword, accountState403Rule });
assertCondition(quota429Rule?.keywords?.includes(keyword), "429 rendered rule must preserve Codex weekly-limit quota keyword", { keyword, quota429Rule }); assertCondition(quota429Rule?.keywords?.includes(keyword), "429 rendered rule must preserve Codex weekly-limit quota keyword", { keyword, quota429Rule });
} }
for (const keyword of ["model_not_found", "no available channel for model"]) {
assertCondition(serviceUnavailable503Rule?.keywords?.includes(keyword), "503 rendered rule must catch upstream model-routing failures", { keyword, serviceUnavailable503Rule });
}
const disabled = renderSub2ApiTempUnschedulableCredentials({ enabled: false, rules: policy.rules }) as { const disabled = renderSub2ApiTempUnschedulableCredentials({ enabled: false, rules: policy.rules }) as {
temp_unschedulable_enabled?: boolean; temp_unschedulable_enabled?: boolean;
@@ -48,6 +52,7 @@ console.log(JSON.stringify({
"temporary unschedulable policy renders to Sub2API credential field names", "temporary unschedulable policy renders to Sub2API credential field names",
"temporary unschedulable rendering follows the input policy without hard-coded policy gates", "temporary unschedulable rendering follows the input policy without hard-coded policy gates",
"Codex weekly-limit prompt keywords render into 403 and 429 cooldown rules", "Codex weekly-limit prompt keywords render into 403 and 429 cooldown rules",
"upstream model-routing failures render into the 503 cooldown rule",
"disabled policies clear runtime rules", "disabled policies clear runtime rules",
], ],
})); }));
+2 -2
View File
@@ -684,9 +684,9 @@ export function defaultCodexTempUnschedulablePolicy(): CodexTempUnschedulablePol
}, },
{ {
statusCode: 503, statusCode: 503,
keywords: ["capacity", "overloaded", "temporarily unavailable", "temporary", "upstream", "recovered upstream error"], keywords: ["capacity", "overloaded", "temporarily unavailable", "temporary", "upstream", "recovered upstream error", "model_not_found", "no available channel for model"],
durationMinutes: 30, durationMinutes: 30,
description: "Service unavailable responses should cool down longer than one-off transient failures.", description: "Service unavailable and upstream model-routing failures should cool down longer than one-off transient failures.",
}, },
{ {
statusCode: 529, statusCode: 529,