diff --git a/actions/setup/js/ai_credits_context.cjs b/actions/setup/js/ai_credits_context.cjs
index 327b51a7c4f..b2134d2630b 100644
--- a/actions/setup/js/ai_credits_context.cjs
+++ b/actions/setup/js/ai_credits_context.cjs
@@ -18,6 +18,10 @@ const BUDGET_EXCEEDED_EVENT = "budget_exceeded";
// The literal error type emitted by the AWF API proxy (HTTP 400) when maxAiCredits is active
// and the requested model is not in the built-in pricing table.
const UNKNOWN_MODEL_AI_CREDITS_TYPE = "unknown_model_ai_credits";
+// The literal error type emitted by the AWF API proxy (HTTP 403) when the consecutive cache
+// miss counter reaches the apiProxy.maxCacheMisses limit. Engine-agnostic: all engines share
+// the same proxy guardrail.
+const MAX_CACHE_MISSES_EXCEEDED_EVENT_TYPE = "max_cache_misses_exceeded";
const MAX_AI_CREDITS_EXCEEDED_STDIO_RE = /maximum ai credits exceeded(?:\s*\((\d+(?:\.\d+)?)\s*\/\s*(\d+(?:\.\d+)?)\))?/i;
const DEFAULT_AGENT_STDIO_LOG = "/tmp/gh-aw/agent-stdio.log";
const AGENT_STDIO_LOG_MAX_TAIL = 64 * 1024; // 64 KB — sufficient for any realistic error block
@@ -425,6 +429,30 @@ function parseUnknownModelAICreditsAndModelFromAuditLog(auditJsonlPathOverride)
);
}
+/**
+ * Detects a `max_cache_misses_exceeded` event from the AWF API proxy event logs.
+ * The proxy emits this HTTP 403 error when the consecutive cache miss counter reaches
+ * the configured `apiProxy.maxCacheMisses` limit. Detection is engine-agnostic:
+ * all agentic engines share the same AWF API proxy guardrail.
+ * Structured entries emitted by the AWF API proxy look like:
+ * { "type": "max_cache_misses_exceeded", "consecutive_cache_misses": 6, "max_cache_misses": 5 }
+ *
+ * @param {string} [eventLogPathOverride]
+ * @returns {boolean}
+ */
+function parseMaxCacheMissesExceededFromEventLog(eventLogPathOverride) {
+ return iterateJSONLFiles(
+ resolveUnknownModelAICreditsLogPaths(eventLogPathOverride),
+ false,
+ content => content.includes(MAX_CACHE_MISSES_EXCEEDED_EVENT_TYPE),
+ (acc, entry) => {
+ if (acc) return true; // already detected, short-circuit
+ return traverseObjectTree(entry, (_key, value) => value === MAX_CACHE_MISSES_EXCEEDED_EVENT_TYPE) || undefined;
+ },
+ acc => acc
+ );
+}
+
/**
* Single-pass combined read of the audit log, returning all AI credits fields at once.
* Used by resolveAICreditsFailureState to avoid reading the same file twice.
@@ -552,5 +580,7 @@ module.exports = {
parseMaxAICreditsExceededFromAuditLog,
parseUnknownModelAICreditsFromAuditLog,
parseUnknownModelAICreditsAndModelFromAuditLog,
+ parseMaxCacheMissesExceededFromEventLog,
resolveAICreditsFailureState,
+ MAX_CACHE_MISSES_EXCEEDED_EVENT_TYPE,
};
diff --git a/actions/setup/js/ai_credits_context.test.cjs b/actions/setup/js/ai_credits_context.test.cjs
index 218700f333d..6b7b5890a48 100644
--- a/actions/setup/js/ai_credits_context.test.cjs
+++ b/actions/setup/js/ai_credits_context.test.cjs
@@ -364,6 +364,60 @@ describe("ai_credits_context parseUnknownModelAICreditsAndModelFromAuditLog", ()
});
});
+describe("ai_credits_context parseMaxCacheMissesExceededFromEventLog", () => {
+ let tmpDir;
+ let parseMaxCacheMissesExceededFromEventLog;
+
+ beforeEach(async () => {
+ vi.resetModules();
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "aic-cache-misses-test-"));
+ delete process.env.GH_AW_AGENT_OUTPUT;
+ const mod = await import("./ai_credits_context.cjs");
+ const exports = mod.default || mod;
+ parseMaxCacheMissesExceededFromEventLog = exports.parseMaxCacheMissesExceededFromEventLog;
+ });
+
+ afterEach(() => {
+ vi.restoreAllMocks();
+ fs.rmSync(tmpDir, { recursive: true, force: true });
+ delete process.env.GH_AW_AGENT_OUTPUT;
+ });
+
+ function writeEventLog(lines, filename = "event-logs.jsonl") {
+ const logDir = path.join(tmpDir, "sandbox", "firewall", "logs", "api-proxy-logs");
+ fs.mkdirSync(logDir, { recursive: true });
+ const logPath = path.join(logDir, filename);
+ fs.writeFileSync(logPath, lines.map(l => JSON.stringify(l)).join("\n") + "\n", "utf8");
+ process.env.GH_AW_AGENT_OUTPUT = path.join(tmpDir, "output.json");
+ return logPath;
+ }
+
+ it("detects max_cache_misses_exceeded in event-logs.jsonl", () => {
+ writeEventLog([{ type: "max_cache_misses_exceeded", consecutive_cache_misses: 6, max_cache_misses: 5 }]);
+ expect(parseMaxCacheMissesExceededFromEventLog()).toBe(true);
+ });
+
+ it("detects max_cache_misses_exceeded in events.jsonl fallback", () => {
+ writeEventLog([{ type: "max_cache_misses_exceeded", consecutive_cache_misses: 7, max_cache_misses: 5 }], "events.jsonl");
+ expect(parseMaxCacheMissesExceededFromEventLog()).toBe(true);
+ });
+
+ it("returns false when no matching event is present", () => {
+ writeEventLog([{ type: "response", status: 200 }]);
+ expect(parseMaxCacheMissesExceededFromEventLog()).toBe(false);
+ });
+
+ it("returns false for missing event log", () => {
+ process.env.GH_AW_AGENT_OUTPUT = path.join(tmpDir, "output.json");
+ expect(parseMaxCacheMissesExceededFromEventLog("/nonexistent/path/event-logs.jsonl")).toBe(false);
+ });
+
+ it("does not detect other error types", () => {
+ writeEventLog([{ type: "unknown_model_ai_credits" }]);
+ expect(parseMaxCacheMissesExceededFromEventLog()).toBe(false);
+ });
+});
+
describe("ai_credits_context parseMaxAICreditsFromAuditLog", () => {
let tmpDir;
/** @type {(path?: string) => string} */
diff --git a/actions/setup/js/detect_agent_errors.cjs b/actions/setup/js/detect_agent_errors.cjs
index 3edb13952be..4c1966f41ef 100644
--- a/actions/setup/js/detect_agent_errors.cjs
+++ b/actions/setup/js/detect_agent_errors.cjs
@@ -45,7 +45,7 @@
const fs = require("fs");
const { MAX_RUNS_EXCEEDED_PATTERNS, isMaxRunsExceededError } = require("./harness_retry_guard.cjs");
-const { parseUnknownModelAICreditsAndModelFromAuditLog } = require("./ai_credits_context.cjs");
+const { parseUnknownModelAICreditsAndModelFromAuditLog, parseMaxCacheMissesExceededFromEventLog } = require("./ai_credits_context.cjs");
const LOG_FILE = "/tmp/gh-aw/agent-stdio.log";
@@ -121,6 +121,17 @@ function buildCombinedPattern(patterns) {
// The pooled per-run invocation budget is saturated — retries cannot make progress.
const INVOCATION_CAP_EXCEEDED_PATTERN = buildCombinedPattern(MAX_RUNS_EXCEEDED_PATTERNS);
+// Pattern: AWF API proxy consecutive cache miss limit exceeded.
+// The AWF API proxy (engine-agnostic) enforces a configurable limit on back-to-back
+// requests that miss the prompt cache (apiProxy.maxCacheMisses, default 5). When the
+// limit is reached it rejects further requests with HTTP 403 and error type
+// "max_cache_misses_exceeded". Two observable forms:
+// 1) JSON error type in provider API response: "max_cache_misses_exceeded"
+// 2) Human-readable message from SDK wrapper: "Maximum consecutive cache misses exceeded"
+// Structured events from the AWF API proxy event log are checked separately via
+// parseMaxCacheMissesExceededFromEventLog().
+const MAX_CACHE_MISSES_EXCEEDED_PATTERN = /(?:\bmax_cache_misses_exceeded\b|\bmaximum\s+consecutive\s+cache\s+misses\s+exceeded\b)/i;
+
/**
* Determines if the collected output contains the observed Copilot/CAPI quota exhaustion error.
* @param {string} output - Collected stdout+stderr from the process
@@ -142,6 +153,17 @@ function isInvocationCapExceededError(output) {
return isMaxRunsExceededError(output);
}
+/**
+ * Determines if the collected output indicates the AWF API proxy cache miss limit is exceeded.
+ * Checks the agent stdio log for the text-form signal. The structured AWF API proxy event log
+ * is checked separately in detectErrors() via parseMaxCacheMissesExceededFromEventLog().
+ * @param {string} output - Collected stdout+stderr from the process
+ * @returns {boolean}
+ */
+function isMaxCacheMissesExceededError(output) {
+ return MAX_CACHE_MISSES_EXCEEDED_PATTERN.test(output);
+}
+
/**
* Normalize model names to a single safe line for GitHub Actions outputs and issue titles.
* @param {string} value
@@ -164,7 +186,7 @@ function extractMissingModelPricingModelName(logContent) {
/**
* Detect known error patterns in a log string and return detection results.
* @param {string} logContent - Contents of the agent stdio log
- * @returns {{ inferenceAccessError: boolean, mcpPolicyError: boolean, agenticEngineTimeout: boolean, modelNotSupportedError: boolean, http400ResponseError: boolean, capiQuotaExceededError: boolean, invocationCapExceeded: boolean, missingModelPricingError: boolean, missingModelPricingModelName: string }}
+ * @returns {{ inferenceAccessError: boolean, mcpPolicyError: boolean, agenticEngineTimeout: boolean, modelNotSupportedError: boolean, http400ResponseError: boolean, capiQuotaExceededError: boolean, invocationCapExceeded: boolean, maxCacheMissesExceeded: boolean, missingModelPricingError: boolean, missingModelPricingModelName: string }}
*/
function detectErrors(logContent) {
const missingModelPricingModelName = extractMissingModelPricingModelName(logContent);
@@ -176,6 +198,7 @@ function detectErrors(logContent) {
http400ResponseError: HTTP_400_RESPONSE_ERROR_PATTERN.test(logContent),
capiQuotaExceededError: isCAPIQuotaExceededError(logContent),
invocationCapExceeded: isInvocationCapExceededError(logContent),
+ maxCacheMissesExceeded: isMaxCacheMissesExceededError(logContent),
missingModelPricingError: missingModelPricingModelName !== "",
missingModelPricingModelName,
};
@@ -183,7 +206,7 @@ function detectErrors(logContent) {
/**
* Build GitHub Actions output lines from detection results.
- * @param {{ inferenceAccessError: boolean, mcpPolicyError: boolean, agenticEngineTimeout: boolean, modelNotSupportedError: boolean, http400ResponseError: boolean, capiQuotaExceededError: boolean, invocationCapExceeded: boolean, missingModelPricingError: boolean, missingModelPricingModelName: string }} results
+ * @param {{ inferenceAccessError: boolean, mcpPolicyError: boolean, agenticEngineTimeout: boolean, modelNotSupportedError: boolean, http400ResponseError: boolean, capiQuotaExceededError: boolean, invocationCapExceeded: boolean, maxCacheMissesExceeded: boolean, missingModelPricingError: boolean, missingModelPricingModelName: string }} results
* @returns {string[]}
*/
function buildOutputLines(results) {
@@ -196,6 +219,7 @@ function buildOutputLines(results) {
`http_400_response_error=${results.http400ResponseError}`,
`capi_quota_exceeded_error=${effectiveCAPIQuotaExceeded}`,
`invocation_cap_exceeded=${results.invocationCapExceeded}`,
+ `max_cache_misses_exceeded=${results.maxCacheMissesExceeded}`,
`missing_model_pricing_error=${results.missingModelPricingError}`,
`missing_model_pricing_model_name=${results.missingModelPricingModelName}`,
];
@@ -203,7 +227,7 @@ function buildOutputLines(results) {
/**
* Write GitHub Actions outputs to $GITHUB_OUTPUT.
- * @param {{ inferenceAccessError: boolean, mcpPolicyError: boolean, agenticEngineTimeout: boolean, modelNotSupportedError: boolean, http400ResponseError: boolean, capiQuotaExceededError: boolean, invocationCapExceeded: boolean, missingModelPricingError: boolean, missingModelPricingModelName: string }} results
+ * @param {{ inferenceAccessError: boolean, mcpPolicyError: boolean, agenticEngineTimeout: boolean, modelNotSupportedError: boolean, http400ResponseError: boolean, capiQuotaExceededError: boolean, invocationCapExceeded: boolean, maxCacheMissesExceeded: boolean, missingModelPricingError: boolean, missingModelPricingModelName: string }} results
*/
function writeOutputs(results) {
const outputFile = process.env.GITHUB_OUTPUT;
@@ -244,8 +268,17 @@ function main() {
process.stderr.write(`[detect-agent-errors] Detected missing model pricing from firewall structured log: model "${auditModelName}" has no AI credits pricing configured\n`);
}
+ // Also check the AWF API proxy event logs for the `max_cache_misses_exceeded` structured
+ // event. This covers all engines since the proxy guardrail fires independently of the
+ // underlying AI engine.
+ const eventLogCacheMissesExceeded = parseMaxCacheMissesExceededFromEventLog();
+ if (eventLogCacheMissesExceeded && !stdioResults.maxCacheMissesExceeded) {
+ process.stderr.write("[detect-agent-errors] Detected max cache misses exceeded from AWF API proxy event log\n");
+ }
+
const results = {
...stdioResults,
+ maxCacheMissesExceeded: stdioResults.maxCacheMissesExceeded || eventLogCacheMissesExceeded,
missingModelPricingError: stdioResults.missingModelPricingError || auditMissingPricing,
missingModelPricingModelName: stdioResults.missingModelPricingModelName || sanitizeModelName(auditModelName),
};
@@ -271,6 +304,9 @@ function main() {
if (results.invocationCapExceeded) {
process.stderr.write("[detect-agent-errors] Detected invocation cap exhaustion: the pooled per-run LLM invocation budget is fully saturated\n");
}
+ if (results.maxCacheMissesExceeded) {
+ process.stderr.write("[detect-agent-errors] Detected max cache misses exceeded: the AWF API proxy consecutive cache miss limit was reached\n");
+ }
if (results.missingModelPricingError && !auditMissingPricing) {
process.stderr.write(`[detect-agent-errors] Detected missing model pricing: model "${results.missingModelPricingModelName}" has no AI credits pricing configured\n`);
}
@@ -287,6 +323,7 @@ module.exports = {
extractMissingModelPricingModelName,
isCAPIQuotaExceededError,
isInvocationCapExceededError,
+ isMaxCacheMissesExceededError,
INFERENCE_ACCESS_ERROR_PATTERN,
MCP_POLICY_BLOCKED_PATTERN,
AGENTIC_ENGINE_TIMEOUT_PATTERN,
@@ -294,6 +331,7 @@ module.exports = {
HTTP_400_RESPONSE_ERROR_PATTERN,
CAPI_QUOTA_EXCEEDED_PATTERN,
INVOCATION_CAP_EXCEEDED_PATTERN,
+ MAX_CACHE_MISSES_EXCEEDED_PATTERN,
MISSING_MODEL_PRICING_PATTERN,
buildOutputLines,
};
diff --git a/actions/setup/js/detect_agent_errors.test.cjs b/actions/setup/js/detect_agent_errors.test.cjs
index 7633d14d591..1ec00d34e9f 100644
--- a/actions/setup/js/detect_agent_errors.test.cjs
+++ b/actions/setup/js/detect_agent_errors.test.cjs
@@ -4,6 +4,7 @@ const {
detectErrors,
isCAPIQuotaExceededError,
isInvocationCapExceededError,
+ isMaxCacheMissesExceededError,
INFERENCE_ACCESS_ERROR_PATTERN,
MCP_POLICY_BLOCKED_PATTERN,
AGENTIC_ENGINE_TIMEOUT_PATTERN,
@@ -11,6 +12,7 @@ const {
HTTP_400_RESPONSE_ERROR_PATTERN,
CAPI_QUOTA_EXCEEDED_PATTERN,
INVOCATION_CAP_EXCEEDED_PATTERN,
+ MAX_CACHE_MISSES_EXCEEDED_PATTERN,
MISSING_MODEL_PRICING_PATTERN,
extractMissingModelPricingModelName,
buildOutputLines,
@@ -318,6 +320,7 @@ describe("detect_agent_errors.cjs", () => {
expect(result.http400ResponseError).toBe(false);
expect(result.capiQuotaExceededError).toBe(false);
expect(result.invocationCapExceeded).toBe(false);
+ expect(result.maxCacheMissesExceeded).toBe(false);
expect(result.missingModelPricingError).toBe(false);
expect(result.missingModelPricingModelName).toBe("");
});
@@ -484,6 +487,7 @@ describe("detect_agent_errors.cjs", () => {
expect(result.http400ResponseError).toBe(false);
expect(result.capiQuotaExceededError).toBe(false);
expect(result.invocationCapExceeded).toBe(false);
+ expect(result.maxCacheMissesExceeded).toBe(false);
expect(result.missingModelPricingError).toBe(false);
});
@@ -515,6 +519,67 @@ commentary" has no AI credits pricing`;
expect(result.missingModelPricingError).toBe(false);
expect(result.missingModelPricingModelName).toBe("");
});
+
+ it("detects max cache misses exceeded (JSON error type form)", () => {
+ const result = detectErrors('{"error":{"type":"max_cache_misses_exceeded","message":"Maximum consecutive cache misses exceeded (6 / 5).","consecutive_cache_misses":6,"max_cache_misses":5}}');
+ expect(result.maxCacheMissesExceeded).toBe(true);
+ expect(result.inferenceAccessError).toBe(false);
+ expect(result.invocationCapExceeded).toBe(false);
+ });
+
+ it("detects max cache misses exceeded (human-readable message form)", () => {
+ const result = detectErrors("Maximum consecutive cache misses exceeded");
+ expect(result.maxCacheMissesExceeded).toBe(true);
+ expect(result.inferenceAccessError).toBe(false);
+ expect(result.invocationCapExceeded).toBe(false);
+ });
+
+ it("detects max cache misses exceeded in a production log line", () => {
+ const log = '2026-07-30T06:14:50.000Z [ERROR] Error in API request: 403 {"error":{"type":"max_cache_misses_exceeded","message":"Maximum consecutive cache misses exceeded (6 / 5).","consecutive_cache_misses":6,"max_cache_misses":5}}';
+ const result = detectErrors(log);
+ expect(result.maxCacheMissesExceeded).toBe(true);
+ });
+
+ it("does not false-positive on unrelated cache miss content", () => {
+ const result = detectErrors("Cache miss for key: model-output-xyz");
+ expect(result.maxCacheMissesExceeded).toBe(false);
+ });
+ });
+
+ describe("MAX_CACHE_MISSES_EXCEEDED_PATTERN", () => {
+ it("matches max_cache_misses_exceeded error type", () => {
+ expect(MAX_CACHE_MISSES_EXCEEDED_PATTERN.test('{"type":"max_cache_misses_exceeded"}')).toBe(true);
+ });
+
+ it("matches Maximum consecutive cache misses exceeded message", () => {
+ expect(MAX_CACHE_MISSES_EXCEEDED_PATTERN.test("Maximum consecutive cache misses exceeded")).toBe(true);
+ });
+
+ it("is case-insensitive", () => {
+ expect(MAX_CACHE_MISSES_EXCEEDED_PATTERN.test("MAXIMUM CONSECUTIVE CACHE MISSES EXCEEDED")).toBe(true);
+ });
+
+ it("does not match unrelated cache miss content", () => {
+ expect(MAX_CACHE_MISSES_EXCEEDED_PATTERN.test("Cache miss for key: output")).toBe(false);
+ });
+ });
+
+ describe("isMaxCacheMissesExceededError", () => {
+ it("returns false for empty input", () => {
+ expect(isMaxCacheMissesExceededError("")).toBe(false);
+ });
+
+ it("detects max_cache_misses_exceeded JSON error type", () => {
+ expect(isMaxCacheMissesExceededError('{"error":{"type":"max_cache_misses_exceeded"}}')).toBe(true);
+ });
+
+ it("detects human-readable message form", () => {
+ expect(isMaxCacheMissesExceededError("Maximum consecutive cache misses exceeded")).toBe(true);
+ });
+
+ it("returns false for unrelated content", () => {
+ expect(isMaxCacheMissesExceededError("Some unrelated error message")).toBe(false);
+ });
});
describe("buildOutputLines", () => {
@@ -542,6 +607,7 @@ commentary" has no AI credits pricing`;
http400ResponseError: false,
capiQuotaExceededError: false,
invocationCapExceeded: false,
+ maxCacheMissesExceeded: false,
missingModelPricingError: true,
missingModelPricingModelName: "claude-opus-5",
});
@@ -559,6 +625,7 @@ commentary" has no AI credits pricing`;
http400ResponseError: false,
capiQuotaExceededError: false,
invocationCapExceeded: false,
+ maxCacheMissesExceeded: false,
missingModelPricingError: false,
missingModelPricingModelName: "",
});
@@ -566,5 +633,39 @@ commentary" has no AI credits pricing`;
expect(lines).toContain("missing_model_pricing_error=false");
expect(lines).toContain("missing_model_pricing_model_name=");
});
+
+ it("emits max_cache_misses_exceeded=true when detected", () => {
+ const lines = buildOutputLines({
+ inferenceAccessError: false,
+ mcpPolicyError: false,
+ agenticEngineTimeout: false,
+ modelNotSupportedError: false,
+ http400ResponseError: false,
+ capiQuotaExceededError: false,
+ invocationCapExceeded: false,
+ maxCacheMissesExceeded: true,
+ missingModelPricingError: false,
+ missingModelPricingModelName: "",
+ });
+
+ expect(lines).toContain("max_cache_misses_exceeded=true");
+ });
+
+ it("emits max_cache_misses_exceeded=false when not detected", () => {
+ const lines = buildOutputLines({
+ inferenceAccessError: false,
+ mcpPolicyError: false,
+ agenticEngineTimeout: false,
+ modelNotSupportedError: false,
+ http400ResponseError: false,
+ capiQuotaExceededError: false,
+ invocationCapExceeded: false,
+ maxCacheMissesExceeded: false,
+ missingModelPricingError: false,
+ missingModelPricingModelName: "",
+ });
+
+ expect(lines).toContain("max_cache_misses_exceeded=false");
+ });
});
});
diff --git a/actions/setup/js/handle_agent_failure.cjs b/actions/setup/js/handle_agent_failure.cjs
index c3b9579f7d9..9648332e61f 100644
--- a/actions/setup/js/handle_agent_failure.cjs
+++ b/actions/setup/js/handle_agent_failure.cjs
@@ -11,7 +11,15 @@ const { MAX_SUB_ISSUES, getSubIssueCount } = require("./sub_issue_helpers.cjs");
const { formatMissingData, formatMissingTools } = require("./missing_info_formatter.cjs");
const { generateHistoryUrl } = require("./generate_history_link.cjs");
const { AWF_INFRA_LINE_RE } = require("./log_parser_shared.cjs");
-const { resolveFirewallAuditLogPath, resolveAICreditsFailureState, parseMaxAICreditsFromAuditLog, parseAICreditsErrorInfoFromAuditLog, parseUnknownModelAICreditsFromAuditLog } = require("./ai_credits_context.cjs");
+const {
+ resolveFirewallAuditLogPath,
+ resolveAICreditsFailureState,
+ parseMaxAICreditsFromAuditLog,
+ parseAICreditsErrorInfoFromAuditLog,
+ parseUnknownModelAICreditsFromAuditLog,
+ parseMaxCacheMissesExceededFromEventLog,
+} = require("./ai_credits_context.cjs");
+const { MAX_CACHE_MISSES_EXCEEDED_PATTERN } = require("./detect_agent_errors.cjs");
const { formatAICCredits } = require("./daily_aic_workflow_helpers.cjs");
const { formatAIC } = require("./model_costs.cjs");
const { parseBoolTemplatable } = require("./templatable.cjs");
@@ -1981,6 +1989,33 @@ function buildEngineMaxRunsExceededContext(engineLabel) {
return "\n" + renderPromptTemplate("engine_max_runs_exceeded.md", { engine_label: normalizedEngineLabel });
}
+/**
+ * Detect max consecutive cache misses failures in text payloads.
+ * Returns true when content includes either the `max_cache_misses_exceeded` error type
+ * or the "Maximum consecutive cache misses exceeded" message fragment.
+ * Uses the shared MAX_CACHE_MISSES_EXCEEDED_PATTERN from detect_agent_errors for
+ * consistency with the unified detection mechanism.
+ * @param {string|null|undefined} content
+ * @returns {boolean}
+ */
+function hasEngineMaxCacheMissesExceededSignal(content) {
+ if (!content) {
+ return false;
+ }
+ return MAX_CACHE_MISSES_EXCEEDED_PATTERN.test(content);
+}
+
+/**
+ * Build dedicated context for max consecutive cache misses failures.
+ * Renders the max-cache-misses-exceeded prompt template with the active engine label.
+ * @param {string} [engineLabel]
+ * @returns {string}
+ */
+function buildEngineMaxCacheMissesExceededContext(engineLabel) {
+ const normalizedEngineLabel = (typeof engineLabel === "string" ? engineLabel : "").trim() || "AI";
+ return "\n" + renderPromptTemplate("max_cache_misses_exceeded.md", { engine_label: normalizedEngineLabel });
+}
+
/**
* Read and render token usage from token-usage.jsonl for inclusion in the ET computation table.
* Returns null gracefully when files are absent, empty, or unparseable.
@@ -2583,6 +2618,7 @@ function detectAWFFirewallStartupFailureFromLog() {
*/
function buildEngineFailureContext(options = {}) {
const suppressEngineRateLimit429 = options.suppressEngineRateLimit429 === true;
+ const maxCacheMissesExceededFromDetection = options.maxCacheMissesExceeded === true;
// Derive agent-stdio.log path from the agent output file path (same directory)
const agentOutputFile = process.env.GH_AW_AGENT_OUTPUT;
const stdioLogPath = agentOutputFile ? path.join(path.dirname(agentOutputFile), "agent-stdio.log") : "/tmp/gh-aw/agent-stdio.log";
@@ -2590,15 +2626,24 @@ function buildEngineFailureContext(options = {}) {
// Include engine ID in failure messages when available (e.g. "copilot", "claude", "codex")
const engineId = process.env.GH_AW_ENGINE_ID || "";
const engineLabel = engineId ? ` \`${engineId}\`` : " AI";
+ const hasStructuredMaxCacheMissesSignal = maxCacheMissesExceededFromDetection || parseMaxCacheMissesExceededFromEventLog();
try {
if (!fs.existsSync(stdioLogPath)) {
+ if (hasStructuredMaxCacheMissesSignal) {
+ core.info("agent-stdio.log not found, but structured max cache misses signal was detected — using dedicated context message");
+ return buildEngineMaxCacheMissesExceededContext(engineLabel);
+ }
core.info(`agent-stdio.log not found at ${stdioLogPath}, skipping engine failure context`);
return "";
}
const logContent = fs.readFileSync(stdioLogPath, "utf8");
if (!logContent.trim()) {
+ if (hasStructuredMaxCacheMissesSignal) {
+ core.info("agent-stdio.log is empty, but structured max cache misses signal was detected — using dedicated context message");
+ return buildEngineMaxCacheMissesExceededContext(engineLabel);
+ }
return "";
}
@@ -2623,6 +2668,11 @@ function buildEngineFailureContext(options = {}) {
return buildEngineMaxRunsExceededContext(engineLabel);
}
+ if (hasEngineMaxCacheMissesExceededSignal(logContent) || hasStructuredMaxCacheMissesSignal) {
+ core.info("Detected engine max cache misses signal — using dedicated context message");
+ return buildEngineMaxCacheMissesExceededContext(engineLabel);
+ }
+
const errorMessages = new Set();
for (const line of lines) {
@@ -3069,6 +3119,7 @@ async function main() {
const agenticEngineTimeout = process.env.GH_AW_AGENTIC_ENGINE_TIMEOUT === "true";
const modelNotSupportedError = process.env.GH_AW_MODEL_NOT_SUPPORTED_ERROR === "true";
const http400ResponseError = process.env.GH_AW_HTTP_400_RESPONSE_ERROR === "true";
+ const maxCacheMissesExceeded = process.env.GH_AW_MAX_CACHE_MISSES_EXCEEDED === "true" && agentConclusion === "failure";
const unknownModelAICreditsFromOutput = process.env.GH_AW_UNKNOWN_MODEL_AI_CREDITS === "true";
const unknownModelAICreditsFromAudit = parseUnknownModelAICreditsFromAuditLog();
const unknownModelAICredits = unknownModelAICreditsFromAudit || (unknownModelAICreditsFromOutput && agentConclusion === "failure");
@@ -3664,7 +3715,12 @@ async function main() {
// context is the more actionable signal.
// Also suppress when missing-model-pricing is detected: the pricing error is the
// root cause and the engine error block would be redundant noise.
- const engineFailureContext = shouldBuildEngineFailureContext(agentConclusion, hasToolDenialsExceeded, isTimedOut, missingModelPricingError) ? buildEngineFailureContext({ suppressEngineRateLimit429: maxAICreditsExceeded }) : "";
+ const engineFailureContext = shouldBuildEngineFailureContext(agentConclusion, hasToolDenialsExceeded, isTimedOut, missingModelPricingError)
+ ? buildEngineFailureContext({
+ suppressEngineRateLimit429: maxAICreditsExceeded,
+ maxCacheMissesExceeded,
+ })
+ : "";
// Build timeout context
const timeoutContext = buildTimeoutContext(isTimedOut, timeoutMinutes);
@@ -4081,6 +4137,8 @@ module.exports = {
hasEngineRateLimit429InOTELMirror,
buildEngineMaxRunsExceededContext,
buildEngineRateLimit429Context,
+ hasEngineMaxCacheMissesExceededSignal,
+ buildEngineMaxCacheMissesExceededContext,
readTokenUsageMarkdown,
parseFirewallAuthErrors,
parseMaxAICreditsFromAuditLog,
diff --git a/actions/setup/js/handle_agent_failure.test.cjs b/actions/setup/js/handle_agent_failure.test.cjs
index 22ed02289f6..d27867e77bb 100644
--- a/actions/setup/js/handle_agent_failure.test.cjs
+++ b/actions/setup/js/handle_agent_failure.test.cjs
@@ -22,6 +22,7 @@ describe("handle_agent_failure", () => {
let getActionFailureIssueExpiresHours;
const ENGINE_RATE_LIMIT_TEMPLATE = "> [!WARNING]\n> **Engine Rate Limited (HTTP 429)**\n> OTLP telemetry\n> {engine_label}\n";
const ENGINE_MAX_RUNS_EXCEEDED_TEMPLATE = "> [!WARNING]\n> **Engine Max Runs Exceeded**\n> max-runs guardrail\n> {engine_label}\n";
+ const ENGINE_MAX_CACHE_MISSES_EXCEEDED_TEMPLATE = "> [!WARNING]\n> **Engine Cache Miss Limit Exceeded**\n> cache misses guardrail\n> {engine_label}\n";
beforeEach(() => {
// Provide minimal GitHub Actions globals expected by require-time code
@@ -2286,6 +2287,7 @@ describe("handle_agent_failure", () => {
fs.mkdirSync(promptsDir, { recursive: true });
fs.writeFileSync(path.join(promptsDir, "engine_rate_limit_429.md"), ENGINE_RATE_LIMIT_TEMPLATE);
fs.writeFileSync(path.join(promptsDir, "engine_max_runs_exceeded.md"), ENGINE_MAX_RUNS_EXCEEDED_TEMPLATE);
+ fs.writeFileSync(path.join(promptsDir, "max_cache_misses_exceeded.md"), ENGINE_MAX_CACHE_MISSES_EXCEEDED_TEMPLATE);
process.env.GH_AW_AGENT_OUTPUT = path.join(tmpDir, "agent_output.json");
process.env.RUNNER_TEMP = tmpDir;
({ buildEngineFailureContext } = require("./handle_agent_failure.cjs"));
@@ -2294,6 +2296,7 @@ describe("handle_agent_failure", () => {
afterEach(() => {
delete process.env.GH_AW_AGENT_OUTPUT;
delete process.env.GH_AW_ENGINE_ID;
+ delete process.env.GH_AW_MAX_CACHE_MISSES_EXCEEDED;
delete process.env.GH_AW_OTEL_JSONL_PATH;
delete process.env.RUNNER_TEMP;
// Clean up temp dir
@@ -2350,6 +2353,28 @@ describe("handle_agent_failure", () => {
expect(result).not.toContain("Last agent output");
});
+ it("returns dedicated context for max-cache-misses failures in stdio logs", () => {
+ fs.writeFileSync(
+ stdioLogPath,
+ '2026-07-30T06:14:50.000Z [ERROR] Error in API request: 403 {"error":{"type":"max_cache_misses_exceeded","message":"Maximum consecutive cache misses exceeded (6 / 5).","consecutive_cache_misses":6,"max_cache_misses":5}}\n'
+ );
+ const result = buildEngineFailureContext();
+ expect(result).toContain("Engine Cache Miss Limit Exceeded");
+ expect(result).toContain("cache misses guardrail");
+ expect(result).not.toContain("Last agent output");
+ });
+
+ it("returns dedicated context when max-cache-misses is only present in structured logs", () => {
+ const logDir = path.join(tmpDir, "sandbox", "firewall", "logs", "api-proxy-logs");
+ fs.mkdirSync(logDir, { recursive: true });
+ fs.writeFileSync(path.join(logDir, "event-logs.jsonl"), `${JSON.stringify({ type: "max_cache_misses_exceeded", consecutive_cache_misses: 6, max_cache_misses: 5 })}\n`);
+ fs.writeFileSync(stdioLogPath, "Agent terminated unexpectedly without clear error details\n");
+ process.env.GH_AW_MAX_CACHE_MISSES_EXCEEDED = "true";
+ const result = buildEngineFailureContext();
+ expect(result).toContain("Engine Cache Miss Limit Exceeded");
+ expect(result).not.toContain("Last agent output");
+ });
+
it("suppresses engine 429 context when max-ai-credits-exceeded takes precedence", () => {
fs.writeFileSync(stdioLogPath, "Failed to get response from the AI model; retried 5 times. Last error: CAPIError: 429 429 Sorry, you've exceeded your rate limit for utility models.\n");
const result = buildEngineFailureContext({ suppressEngineRateLimit429: true });
@@ -2920,6 +2945,95 @@ describe("handle_agent_failure", () => {
});
});
+ // ──────────────────────────────────────────────────────
+ // hasEngineMaxCacheMissesExceededSignal
+ // ──────────────────────────────────────────────────────
+
+ describe("hasEngineMaxCacheMissesExceededSignal", () => {
+ let hasEngineMaxCacheMissesExceededSignal;
+
+ beforeEach(() => {
+ vi.resetModules();
+ ({ hasEngineMaxCacheMissesExceededSignal } = require("./handle_agent_failure.cjs"));
+ });
+
+ it("returns false for empty-like content", () => {
+ expect(hasEngineMaxCacheMissesExceededSignal("")).toBe(false);
+ expect(hasEngineMaxCacheMissesExceededSignal(null)).toBe(false);
+ expect(hasEngineMaxCacheMissesExceededSignal(undefined)).toBe(false);
+ });
+
+ it("returns true when max_cache_misses_exceeded marker is present", () => {
+ expect(hasEngineMaxCacheMissesExceededSignal('{"error":{"type":"max_cache_misses_exceeded"}}')).toBe(true);
+ });
+
+ it("returns true when Maximum consecutive cache misses exceeded text is present", () => {
+ expect(hasEngineMaxCacheMissesExceededSignal("Maximum consecutive cache misses exceeded (6 / 5).")).toBe(true);
+ });
+
+ it("returns true for the exact error seen in production logs", () => {
+ const logLine =
+ '2026-07-30T06:14:50.000Z [ERROR] Error in API request: 403 {"error":{"type":"max_cache_misses_exceeded","message":"Maximum consecutive cache misses exceeded (6 / 5).","consecutive_cache_misses":6,"max_cache_misses":5}}';
+ expect(hasEngineMaxCacheMissesExceededSignal(logLine)).toBe(true);
+ });
+
+ it("returns false for unrelated content", () => {
+ expect(hasEngineMaxCacheMissesExceededSignal("request failed for unrelated reason")).toBe(false);
+ });
+ });
+
+ // ──────────────────────────────────────────────────────
+ // buildEngineMaxCacheMissesExceededContext
+ // ──────────────────────────────────────────────────────
+
+ describe("buildEngineMaxCacheMissesExceededContext", () => {
+ let buildEngineMaxCacheMissesExceededContext;
+ const fs = require("fs");
+ const path = require("path");
+ const os = require("os");
+
+ /** @type {string} */
+ let tmpDir;
+
+ /** @type {string} */
+ let promptsDir;
+
+ beforeEach(() => {
+ vi.resetModules();
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "aw-test-cache-misses-"));
+ promptsDir = path.join(tmpDir, "gh-aw", "prompts");
+ fs.mkdirSync(promptsDir, { recursive: true });
+ process.env.RUNNER_TEMP = tmpDir;
+ ({ buildEngineMaxCacheMissesExceededContext } = require("./handle_agent_failure.cjs"));
+ fs.writeFileSync(path.join(promptsDir, "max_cache_misses_exceeded.md"), ENGINE_MAX_CACHE_MISSES_EXCEEDED_TEMPLATE);
+ });
+
+ afterEach(() => {
+ delete process.env.RUNNER_TEMP;
+ if (fs.existsSync(tmpDir)) {
+ fs.rmSync(tmpDir, { recursive: true, force: true });
+ }
+ });
+
+ it("renders template content for a provided engine label", () => {
+ const result = buildEngineMaxCacheMissesExceededContext("claude");
+ expect(result).toContain("Engine Cache Miss Limit Exceeded");
+ expect(result).toContain("cache misses guardrail");
+ expect(result).toContain("claude");
+ });
+
+ it("falls back to AI when engine label is empty or whitespace", () => {
+ expect(buildEngineMaxCacheMissesExceededContext("")).toContain("AI");
+ expect(buildEngineMaxCacheMissesExceededContext(" ")).toContain("AI");
+ });
+
+ it("trims leading/trailing whitespace from engine label", () => {
+ const result = buildEngineMaxCacheMissesExceededContext(" claude ");
+ expect(result).toContain("claude");
+ expect(result).not.toContain(" claude ");
+ });
+ });
+
// ──────────────────────────────────────────────────────
// buildMCPPolicyErrorContext
// ──────────────────────────────────────────────────────
diff --git a/actions/setup/md/max_cache_misses_exceeded.md b/actions/setup/md/max_cache_misses_exceeded.md
new file mode 100644
index 00000000000..232840abd12
--- /dev/null
+++ b/actions/setup/md/max_cache_misses_exceeded.md
@@ -0,0 +1,28 @@
+> [!WARNING]
+> **Engine Cache Miss Limit Exceeded**: The {engine_label} engine hit the gh-aw API proxy consecutive cache miss guardrail and could not complete this run.
+
+This signal was detected from engine runtime or AWF API proxy logs.
+
+
+What caused this
+
+The gh-aw API proxy enforces a per-run `apiProxy.maxCacheMisses` guardrail. When too many back-to-back requests bypass the prompt cache, the proxy returns a 403 `max_cache_misses_exceeded` error and the engine terminates.
+
+Common causes:
+
+- Dynamic or frequently changing content in the prompt (e.g. timestamps, run IDs, random values)
+- Prompts that are too short for the provider's cache threshold
+- High concurrency across workflow runs that share the same cache slot
+- A burst of runs before the cache warms up after a deployment or key rotation
+
+
+
+
+How to remediate
+
+- **Wait and retry**: Cache misses are often transient. Re-running the workflow after a few minutes is usually sufficient.
+- **Stabilize the prompt**: Move volatile values (current date, run ID, etc.) out of the system prompt or early turns and into later user turns so the cacheable prefix stays constant.
+- **Reduce run frequency**: If the workflow fires too often, the cache slot may be evicted before reuse. Consider scheduling less aggressively or adding a concurrency group.
+- **Review `max-daily-ai-credits`**: A very high run volume can exhaust per-key cache capacity. Check your usage and apply a daily cap if needed.
+
+
diff --git a/pkg/workflow/compiler_main_job_helpers.go b/pkg/workflow/compiler_main_job_helpers.go
index 5139f6bc875..b412a53fec2 100644
--- a/pkg/workflow/compiler_main_job_helpers.go
+++ b/pkg/workflow/compiler_main_job_helpers.go
@@ -198,6 +198,8 @@ func (c *Compiler) addMainJobEngineErrorOutputs(outputs map[string]string, data
compilerMainJobLog.Printf("Added http_400_response_error output (engine=%s, step=%s)", engine.GetID(), constants.DetectAgentErrorsStepID)
outputs["invocation_cap_exceeded"] = fmt.Sprintf("${{ %s.invocation_cap_exceeded || 'false' }}", stepRef)
compilerMainJobLog.Printf("Added invocation_cap_exceeded output (engine=%s, step=%s)", engine.GetID(), constants.DetectAgentErrorsStepID)
+ outputs["max_cache_misses_exceeded"] = fmt.Sprintf("${{ %s.max_cache_misses_exceeded || 'false' }}", stepRef)
+ compilerMainJobLog.Printf("Added max_cache_misses_exceeded output (engine=%s, step=%s)", engine.GetID(), constants.DetectAgentErrorsStepID)
outputs["missing_model_pricing_error"] = fmt.Sprintf("${{ %s.missing_model_pricing_error || 'false' }}", stepRef)
compilerMainJobLog.Printf("Added missing_model_pricing_error output (engine=%s, step=%s)", engine.GetID(), constants.DetectAgentErrorsStepID)
outputs["missing_model_pricing_model_name"] = fmt.Sprintf("${{ %s.missing_model_pricing_model_name || '' }}", stepRef)
diff --git a/pkg/workflow/notify_comment_conclusion_helpers.go b/pkg/workflow/notify_comment_conclusion_helpers.go
index e2401b69a78..13e6ccd4e97 100644
--- a/pkg/workflow/notify_comment_conclusion_helpers.go
+++ b/pkg/workflow/notify_comment_conclusion_helpers.go
@@ -247,7 +247,7 @@ func buildAgentFailureEngineDetectionVars(engine CodingAgentEngine, data *Workfl
// Pass engine error-detection outputs to the conclusion job when the selected engine
// provides a host-runner detect-agent-errors step.
// Contract: engines returning a non-empty GetErrorDetectionScriptId() must run
- // actions/setup/js/detect_agent_errors.cjs, which emits all six outputs below.
+ // actions/setup/js/detect_agent_errors.cjs, which emits all outputs below.
// These outputs cover:
// - inference_access_error: token lacks inference access
// - mcp_policy_error: MCP servers blocked by enterprise/organization policy
@@ -255,6 +255,7 @@ func buildAgentFailureEngineDetectionVars(engine CodingAgentEngine, data *Workfl
// - model_not_supported_error: configured model name is invalid or unavailable
// - http_400_response_error: engine returned a generic HTTP 400 Bad Request response
// - capi_quota_exceeded_error: Copilot/CAPI quota exhaustion/rate-limit response
+ // - max_cache_misses_exceeded: AWF API proxy consecutive cache miss guardrail fired
var envVars []string
if engine.GetErrorDetectionScriptId() != "" {
envVars = append(envVars, fmt.Sprintf(" GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.%s.outputs.inference_access_error }}\n", mainJobName))
@@ -262,6 +263,7 @@ func buildAgentFailureEngineDetectionVars(engine CodingAgentEngine, data *Workfl
envVars = append(envVars, fmt.Sprintf(" GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.%s.outputs.agentic_engine_timeout }}\n", mainJobName))
envVars = append(envVars, fmt.Sprintf(" GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.%s.outputs.model_not_supported_error }}\n", mainJobName))
envVars = append(envVars, fmt.Sprintf(" GH_AW_HTTP_400_RESPONSE_ERROR: ${{ needs.%s.outputs.http_400_response_error }}\n", mainJobName))
+ envVars = append(envVars, fmt.Sprintf(" GH_AW_MAX_CACHE_MISSES_EXCEEDED: ${{ needs.%s.outputs.max_cache_misses_exceeded }}\n", mainJobName))
envVars = append(envVars, fmt.Sprintf(" GH_AW_MISSING_MODEL_PRICING_ERROR: ${{ needs.%s.outputs.missing_model_pricing_error }}\n", mainJobName))
envVars = append(envVars, fmt.Sprintf(" GH_AW_MISSING_MODEL_PRICING_MODEL_NAME: ${{ needs.%s.outputs.missing_model_pricing_model_name }}\n", mainJobName))
}
diff --git a/pkg/workflow/testdata/TestWasmGolden_AllEngines/claude.golden b/pkg/workflow/testdata/TestWasmGolden_AllEngines/claude.golden
index 61e90e147af..c21aed832ee 100644
--- a/pkg/workflow/testdata/TestWasmGolden_AllEngines/claude.golden
+++ b/pkg/workflow/testdata/TestWasmGolden_AllEngines/claude.golden
@@ -337,6 +337,7 @@ jobs:
http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }}
inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }}
invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }}
+ max_cache_misses_exceeded: ${{ steps.detect-agent-errors.outputs.max_cache_misses_exceeded || 'false' }}
mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }}
missing_model_pricing_error: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_error || 'false' }}
missing_model_pricing_model_name: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_model_name || '' }}
diff --git a/pkg/workflow/testdata/TestWasmGolden_AllEngines/codex.golden b/pkg/workflow/testdata/TestWasmGolden_AllEngines/codex.golden
index 1a67fdc957e..138f8c1bfae 100644
--- a/pkg/workflow/testdata/TestWasmGolden_AllEngines/codex.golden
+++ b/pkg/workflow/testdata/TestWasmGolden_AllEngines/codex.golden
@@ -338,6 +338,7 @@ jobs:
http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }}
inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }}
invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }}
+ max_cache_misses_exceeded: ${{ steps.detect-agent-errors.outputs.max_cache_misses_exceeded || 'false' }}
mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }}
missing_model_pricing_error: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_error || 'false' }}
missing_model_pricing_model_name: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_model_name || '' }}
diff --git a/pkg/workflow/testdata/TestWasmGolden_AllEngines/copilot.golden b/pkg/workflow/testdata/TestWasmGolden_AllEngines/copilot.golden
index e3896b30c7c..44d6d372205 100644
--- a/pkg/workflow/testdata/TestWasmGolden_AllEngines/copilot.golden
+++ b/pkg/workflow/testdata/TestWasmGolden_AllEngines/copilot.golden
@@ -337,6 +337,7 @@ jobs:
http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }}
inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }}
invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }}
+ max_cache_misses_exceeded: ${{ steps.detect-agent-errors.outputs.max_cache_misses_exceeded || 'false' }}
mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }}
missing_model_pricing_error: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_error || 'false' }}
missing_model_pricing_model_name: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_model_name || '' }}
diff --git a/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/basic-copilot.golden b/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/basic-copilot.golden
index 365414c342f..808d00196a2 100644
--- a/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/basic-copilot.golden
+++ b/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/basic-copilot.golden
@@ -337,6 +337,7 @@ jobs:
http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }}
inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }}
invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }}
+ max_cache_misses_exceeded: ${{ steps.detect-agent-errors.outputs.max_cache_misses_exceeded || 'false' }}
mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }}
missing_model_pricing_error: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_error || 'false' }}
missing_model_pricing_model_name: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_model_name || '' }}
diff --git a/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/playwright-cli-mode.golden b/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/playwright-cli-mode.golden
index b62d59586f6..7203909b84e 100644
--- a/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/playwright-cli-mode.golden
+++ b/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/playwright-cli-mode.golden
@@ -348,6 +348,7 @@ jobs:
http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }}
inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }}
invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }}
+ max_cache_misses_exceeded: ${{ steps.detect-agent-errors.outputs.max_cache_misses_exceeded || 'false' }}
mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }}
missing_model_pricing_error: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_error || 'false' }}
missing_model_pricing_model_name: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_model_name || '' }}
diff --git a/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/smoke-copilot.golden b/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/smoke-copilot.golden
index c3a7e50ea0b..43348eae603 100644
--- a/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/smoke-copilot.golden
+++ b/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/smoke-copilot.golden
@@ -459,6 +459,7 @@ jobs:
http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }}
inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }}
invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }}
+ max_cache_misses_exceeded: ${{ steps.detect-agent-errors.outputs.max_cache_misses_exceeded || 'false' }}
mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }}
missing_model_pricing_error: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_error || 'false' }}
missing_model_pricing_model_name: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_model_name || '' }}
diff --git a/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/with-imports.golden b/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/with-imports.golden
index c149e110815..449603bbfeb 100644
--- a/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/with-imports.golden
+++ b/pkg/workflow/testdata/TestWasmGolden_CompileFixtures/with-imports.golden
@@ -338,6 +338,7 @@ jobs:
http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }}
inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }}
invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }}
+ max_cache_misses_exceeded: ${{ steps.detect-agent-errors.outputs.max_cache_misses_exceeded || 'false' }}
mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }}
missing_model_pricing_error: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_error || 'false' }}
missing_model_pricing_model_name: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_model_name || '' }}