From 59b265f5e0f9b3e74d69d27fa65a116f5bcc890e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Aug 2026 02:55:14 +0000 Subject: [PATCH 1/6] Initial plan From f24db41f791bed402db63186fd66a84fc5726326 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Aug 2026 03:08:26 +0000 Subject: [PATCH 2/6] Treat AWF proxy HTTP 403 max-AI-credits rejection as trusted budget abort Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/claude_harness.cjs | 13 +++++-- actions/setup/js/claude_harness.test.cjs | 38 +++++++++++++++++++ actions/setup/js/codex_harness.cjs | 13 +++++-- actions/setup/js/copilot_harness.cjs | 13 +++++-- actions/setup/js/harness_retry_guard.cjs | 29 ++++++++++++++ actions/setup/js/harness_retry_guard.test.cjs | 19 +++++++++- 6 files changed, 115 insertions(+), 10 deletions(-) diff --git a/actions/setup/js/claude_harness.cjs b/actions/setup/js/claude_harness.cjs index 385d8c13541..962294e329f 100644 --- a/actions/setup/js/claude_harness.cjs +++ b/actions/setup/js/claude_harness.cjs @@ -57,7 +57,7 @@ const { } = require("./awf_reflect.cjs"); const { emitMissingToolPermissionIssue, hasExpectedSafeOutputs, hasNoopInSafeOutputs } = require("./safeoutputs_cli.cjs"); const { countPermissionDeniedIssues, hasNumerousPermissionDeniedIssues, extractDeniedCommands, buildMissingToolPermissionIssuePayload } = require("./permission_denied_helpers.cjs"); -const { detectNonRetryableHarnessGuard, buildSoftTimeoutGuard, emitSoftTimeoutSignal, isAuthenticationFailedError } = require("./harness_retry_guard.cjs"); +const { detectNonRetryableHarnessGuard, buildSoftTimeoutGuard, emitSoftTimeoutSignal, isAuthenticationFailedError, parseAICreditsExceededProxyRejection } = require("./harness_retry_guard.cjs"); const { isCrashSignalExitCode, crashSignalNameForExitCode } = require("./harness_crash_signals.cjs"); const { MODEL_NOT_SUPPORTED_PATTERN: INVALID_MODEL_ERROR_PATTERN } = require("./detect_agent_errors.cjs"); const { applyModelFallback } = require("./model_fallback.cjs"); @@ -528,11 +528,18 @@ async function main() { } const nonRetryableGuard = detectNonRetryableHarnessGuard(result.output); - const trustedAICreditsExceeded = nonRetryableGuard.aiCreditsExceeded && parseMaxAICreditsExceededFromAuditLog(); + const proxyAICreditsRejection = parseAICreditsExceededProxyRejection(result.output); + if (proxyAICreditsRejection) { + log(`attempt ${attempt + 1}: AWF API proxy rejected the request with HTTP 403 max-AI-credits (${proxyAICreditsRejection.aiCredits}/${proxyAICreditsRejection.maxAICredits}) — trusted budget-abort evidence`); + } + const trustedAICreditsExceeded = nonRetryableGuard.aiCreditsExceeded && (!!proxyAICreditsRejection || parseMaxAICreditsExceededFromAuditLog()); if (nonRetryableGuard.aiCreditsExceeded && !trustedAICreditsExceeded) { log(`attempt ${attempt + 1}: AI credits marker found in CLI output without trusted firewall audit confirmation — preserving normal failure handling`); } - const shouldTreatAICreditsExceededAsSuccess = trustedAICreditsExceeded && !isAuthenticationFailed; + // Some CLIs surface the proxy's budget rejection as an authentication failure (e.g. Claude Code + // reports `error: authentication_failed` for "403 Maximum AI credits exceeded"). When the trusted + // proxy signature is present that veto must not mask intentional budget enforcement. + const shouldTreatAICreditsExceededAsSuccess = trustedAICreditsExceeded && (!isAuthenticationFailed || !!proxyAICreditsRejection); if (shouldTreatAICreditsExceededAsSuccess || nonRetryableGuard.awfAPIProxyBlockingRequests || nonRetryableGuard.maxRunsExceeded) { const reasons = []; if (shouldTreatAICreditsExceededAsSuccess) reasons.push("AI credits budget exceeded"); diff --git a/actions/setup/js/claude_harness.test.cjs b/actions/setup/js/claude_harness.test.cjs index 8d20d28fe0c..ebc4d65494c 100644 --- a/actions/setup/js/claude_harness.test.cjs +++ b/actions/setup/js/claude_harness.test.cjs @@ -1054,6 +1054,44 @@ process.exit(1);`, expect(result.stderr).not.toContain("AI credits budget enforced"); }); + it("exits 0 when the AWF API proxy returns HTTP 403 max-AI-credits as an authentication failure", () => { + // Reproduces https://github.com/github/gh-aw/actions/runs/32683896339/job/97305497380: + // Claude Code reports the proxy budget abort as `error: authentication_failed`, and the + // firewall audit JSONL is only written during container teardown — after this decision. + const tempDir = makeHarnessTempDir("claude-ai-credits-proxy-403-"); + const safeOutputsPath = path.join(tempDir, "safe-outputs.jsonl"); + const stubPath = path.join(tempDir, "stub.cjs"); + const promptPath = path.join(tempDir, "prompt.txt"); + const callsPath = path.join(tempDir, "calls.jsonl"); + fs.writeFileSync( + stubPath, + `const fs = require("fs"); +const callsPath = process.env.CLAUDE_HARNESS_STUB_CALLS; +fs.appendFileSync(callsPath, JSON.stringify({args: process.argv.slice(2)}) + "\\n"); +process.stdout.write(JSON.stringify({type: "assistant", message: {model: "", role: "assistant", content: [{type: "text", text: "Failed to authenticate. API Error: 403 Maximum AI credits exceeded (302.111025 / 300)."}]}, error: "authentication_failed", is_api_error_message: true}) + "\\n"); +process.exit(1);`, + "utf8" + ); + fs.writeFileSync(promptPath, "do some work", "utf8"); + + const result = spawnSync(process.execPath, ["claude_harness.cjs", process.execPath, stubPath, "--print", "--prompt-file", promptPath], { + cwd: path.dirname(require.resolve("./claude_harness.cjs")), + env: { + ...process.env, + CLAUDE_HARNESS_STUB_CALLS: callsPath, + GH_AW_SAFE_OUTPUTS: safeOutputsPath, + GH_AW_HARNESS_MAX_RETRIES: "0", + }, + encoding: "utf8", + timeout: 10000, + }); + const callCount = fs.readFileSync(callsPath, "utf8").trim().split("\n").filter(Boolean).length; + expect(callCount).toBe(1); + expect(result.status).toBe(0); + expect(result.stderr).toContain("trusted budget-abort evidence"); + expect(result.stderr).toContain("AI credits budget enforced"); + }); + it("keeps non-zero exit when AI-credit marker appears without trusted firewall audit evidence", () => { const tempDir = makeHarnessTempDir("claude-ai-credits-untrusted-"); const safeOutputsPath = path.join(tempDir, "safe-outputs.jsonl"); diff --git a/actions/setup/js/codex_harness.cjs b/actions/setup/js/codex_harness.cjs index a8a0dd7e1db..3feb700bd1f 100644 --- a/actions/setup/js/codex_harness.cjs +++ b/actions/setup/js/codex_harness.cjs @@ -52,7 +52,7 @@ const { } = require("./awf_reflect.cjs"); const { emitMissingToolPermissionIssue, hasExpectedSafeOutputs, hasNoopInSafeOutputs } = require("./safeoutputs_cli.cjs"); const { countPermissionDeniedIssues, hasNumerousPermissionDeniedIssues, extractDeniedCommands, buildMissingToolPermissionIssuePayload } = require("./permission_denied_helpers.cjs"); -const { detectNonRetryableHarnessGuard, buildSoftTimeoutGuard, emitSoftTimeoutSignal, isAuthenticationFailedError } = require("./harness_retry_guard.cjs"); +const { detectNonRetryableHarnessGuard, buildSoftTimeoutGuard, emitSoftTimeoutSignal, isAuthenticationFailedError, parseAICreditsExceededProxyRejection } = require("./harness_retry_guard.cjs"); const { MODEL_NOT_SUPPORTED_PATTERN: INVALID_MODEL_ERROR_PATTERN } = require("./detect_agent_errors.cjs"); const { resolveRetryConfig } = require("./harness_retry_config.cjs"); const { applyModelFallback, injectModelFlagAfterExec } = require("./model_fallback.cjs"); @@ -691,11 +691,18 @@ async function main() { } const nonRetryableGuard = detectNonRetryableHarnessGuard(result.output); - const trustedAICreditsExceeded = nonRetryableGuard.aiCreditsExceeded && parseMaxAICreditsExceededFromAuditLog(); + const proxyAICreditsRejection = parseAICreditsExceededProxyRejection(result.output); + if (proxyAICreditsRejection) { + log(`attempt ${attempt + 1}: AWF API proxy rejected the request with HTTP 403 max-AI-credits (${proxyAICreditsRejection.aiCredits}/${proxyAICreditsRejection.maxAICredits}) — trusted budget-abort evidence`); + } + const trustedAICreditsExceeded = nonRetryableGuard.aiCreditsExceeded && (!!proxyAICreditsRejection || parseMaxAICreditsExceededFromAuditLog()); if (nonRetryableGuard.aiCreditsExceeded && !trustedAICreditsExceeded) { log(`attempt ${attempt + 1}: AI credits marker found in CLI output without trusted firewall audit confirmation — preserving normal failure handling`); } - const shouldTreatAICreditsExceededAsSuccess = trustedAICreditsExceeded && !isAuthenticationFailed && !isMissingApiKey; + // Some CLIs surface the proxy's budget rejection as an authentication failure (e.g. Claude Code + // reports `error: authentication_failed` for "403 Maximum AI credits exceeded"). When the trusted + // proxy signature is present that veto must not mask intentional budget enforcement. + const shouldTreatAICreditsExceededAsSuccess = trustedAICreditsExceeded && (!isAuthenticationFailed || !!proxyAICreditsRejection) && !isMissingApiKey; if (shouldTreatAICreditsExceededAsSuccess || nonRetryableGuard.awfAPIProxyBlockingRequests || nonRetryableGuard.goalAlreadyActive || nonRetryableGuard.maxRunsExceeded) { const reasons = []; if (shouldTreatAICreditsExceededAsSuccess) reasons.push("AI credits budget exceeded"); diff --git a/actions/setup/js/copilot_harness.cjs b/actions/setup/js/copilot_harness.cjs index 430f7651f06..62b0b88e1ad 100644 --- a/actions/setup/js/copilot_harness.cjs +++ b/actions/setup/js/copilot_harness.cjs @@ -81,7 +81,7 @@ const { } = require("./awf_reflect.cjs"); const { runSafeOutputsCLI, buildMissingToolAlternatives, emitMissingToolPermissionIssue, emitInfrastructureIncomplete, hasExpectedSafeOutputs, hasNoopInSafeOutputs } = require("./safeoutputs_cli.cjs"); const { countPermissionDeniedIssues, hasNumerousPermissionDeniedIssues, extractDeniedCommands, buildMissingToolPermissionIssuePayload } = require("./permission_denied_helpers.cjs"); -const { detectNonRetryableHarnessGuard, buildSoftTimeoutGuard, emitSoftTimeoutSignal, isAuthenticationFailedError: isCommonAuthenticationFailedError } = require("./harness_retry_guard.cjs"); +const { detectNonRetryableHarnessGuard, buildSoftTimeoutGuard, emitSoftTimeoutSignal, isAuthenticationFailedError: isCommonAuthenticationFailedError, parseAICreditsExceededProxyRejection } = require("./harness_retry_guard.cjs"); const { isCrashSignalExitCode, crashSignalNameForExitCode } = require("./harness_crash_signals.cjs"); const { isCAPIQuotaExceededError } = require("./detect_agent_errors.cjs"); const { applyModelFallback } = require("./model_fallback.cjs"); @@ -1456,11 +1456,18 @@ async function main() { return { action: "stop", exitCode: 0 }; } - const trustedAICreditsExceeded = nonRetryableGuard.aiCreditsExceeded && parseMaxAICreditsExceededFromAuditLog(); + const proxyAICreditsRejection = parseAICreditsExceededProxyRejection(result.output); + if (proxyAICreditsRejection) { + log(`attempt ${attempt + 1}: AWF API proxy rejected the request with HTTP 403 max-AI-credits (${proxyAICreditsRejection.aiCredits}/${proxyAICreditsRejection.maxAICredits}) — trusted budget-abort evidence`); + } + const trustedAICreditsExceeded = nonRetryableGuard.aiCreditsExceeded && (!!proxyAICreditsRejection || parseMaxAICreditsExceededFromAuditLog()); if (nonRetryableGuard.aiCreditsExceeded && !trustedAICreditsExceeded) { log(`attempt ${attempt + 1}: AI credits marker found in CLI output without trusted firewall audit confirmation — preserving normal failure handling`); } - const shouldTreatAICreditsExceededAsSuccess = trustedAICreditsExceeded && !isAuthenticationFailed; + // Some CLIs surface the proxy's budget rejection as an authentication failure (e.g. Claude Code + // reports `error: authentication_failed` for "403 Maximum AI credits exceeded"). When the trusted + // proxy signature is present that veto must not mask intentional budget enforcement. + const shouldTreatAICreditsExceededAsSuccess = trustedAICreditsExceeded && (!isAuthenticationFailed || !!proxyAICreditsRejection); if (shouldTreatAICreditsExceededAsSuccess || isInvocationCapExceeded) { const reasons = []; if (shouldTreatAICreditsExceededAsSuccess) reasons.push("AI credits budget exceeded"); diff --git a/actions/setup/js/harness_retry_guard.cjs b/actions/setup/js/harness_retry_guard.cjs index e5e7653cbe8..dbefeed522f 100644 --- a/actions/setup/js/harness_retry_guard.cjs +++ b/actions/setup/js/harness_retry_guard.cjs @@ -10,6 +10,15 @@ const SOFT_TIMEOUT_BUFFER_MS = 90 * 1000; const AI_CREDITS_EXCEEDED_PATTERNS = [/\bmax[\s_-]*ai[\s_-]*credits[\s_-]*exceeded\b/i, /\bai[\s_-]*credits[\s_-]*rate[\s_-]*limit[\s_-]*error\b/i, /ai[\s_-]*credits?.*(?:rate[\s-]*limit|limit exceeded|budget exceeded|exceeded)/i]; +// Canonical rejection emitted by the AWF API proxy once the configured `apiProxy.maxAiCredits` +// budget is exhausted: an HTTP 403 whose message carries the proxy-computed used/max pair, e.g. +// "API Error: 403 Maximum AI credits exceeded (302.111025 / 300)." +// Only the proxy can answer a provider request with this status/message pair, so it is treated as +// trusted evidence of budget enforcement. This matters because the firewall audit JSONL — the other +// trusted source — is only flushed during container teardown, which happens after the harness has +// already classified the failed attempt. +const AI_CREDITS_EXCEEDED_PROXY_REJECTION_RE = /\b403\b[^\n]{0,80}?maximum ai credits exceeded\s*\(\s*(\d+(?:\.\d+)?)\s*\/\s*(\d+(?:\.\d+)?)\s*\)/i; + const AWF_API_PROXY_BLOCKING_REQUESTS_PATTERNS = [/\bawf\b.*\bapi[\s_-]*proxy\b.*\bblocking requests\b/i, /\bapi[\s_-]*proxy\b.*\bblocking requests\b/i, /\bapi[\s_-]*proxy\b.*\bblocked requests?\b/i, /\bDIFC_FILTERED\b/]; const GOAL_ALREADY_ACTIVE_PATTERNS = [/\bthis thread already has a goal\b[\s\S]*?\buse update_goal\b/i, /\bcannot create a new goal because this thread has an unfinished goal\b;\s*\bcomplete the existing goal first\b/i]; @@ -47,6 +56,24 @@ function isAuthenticationFailedError(output) { return AUTHENTICATION_FAILED_PATTERNS.some(pattern => pattern.test(safeOutput)); } +/** + * Extracts the AWF API proxy AI-credits budget rejection (HTTP 403) from harness output. + * Returns null when the output does not carry the proxy signature, or when the reported + * usage does not actually reach the reported budget. + * @param {unknown} output + * @returns {{ aiCredits: number, maxAICredits: number } | null} + */ +function parseAICreditsExceededProxyRejection(output) { + const safeOutput = typeof output === "string" ? output : ""; + const match = AI_CREDITS_EXCEEDED_PROXY_REJECTION_RE.exec(safeOutput); + if (!match) return null; + const aiCredits = Number.parseFloat(match[1]); + const maxAICredits = Number.parseFloat(match[2]); + if (!Number.isFinite(aiCredits) || !Number.isFinite(maxAICredits) || maxAICredits <= 0) return null; + if (aiCredits < maxAICredits) return null; + return { aiCredits, maxAICredits }; +} + /** * Detect retry guard conditions that should stop harness retries immediately. * @param {unknown} output @@ -95,12 +122,14 @@ if (typeof module !== "undefined" && module.exports) { module.exports = { detectNonRetryableHarnessGuard, AI_CREDITS_EXCEEDED_PATTERNS, + AI_CREDITS_EXCEEDED_PROXY_REJECTION_RE, AWF_API_PROXY_BLOCKING_REQUESTS_PATTERNS, GOAL_ALREADY_ACTIVE_PATTERNS, MAX_RUNS_EXCEEDED_PATTERNS, AUTHENTICATION_FAILED_PATTERNS, isMaxRunsExceededError, isAuthenticationFailedError, + parseAICreditsExceededProxyRejection, SOFT_TIMEOUT_BUFFER_MS, buildSoftTimeoutGuard, emitSoftTimeoutSignal, diff --git a/actions/setup/js/harness_retry_guard.test.cjs b/actions/setup/js/harness_retry_guard.test.cjs index 5e4d214db73..0ad8eac1eb5 100644 --- a/actions/setup/js/harness_retry_guard.test.cjs +++ b/actions/setup/js/harness_retry_guard.test.cjs @@ -4,7 +4,7 @@ import { describe, expect, it } from "vitest"; import { createRequire } from "node:module"; const require = createRequire(import.meta.url); -const { detectNonRetryableHarnessGuard, buildSoftTimeoutGuard, isMaxRunsExceededError, isAuthenticationFailedError } = require("./harness_retry_guard.cjs"); +const { detectNonRetryableHarnessGuard, buildSoftTimeoutGuard, isMaxRunsExceededError, isAuthenticationFailedError, parseAICreditsExceededProxyRejection } = require("./harness_retry_guard.cjs"); describe("harness_retry_guard.cjs", () => { it("detects AI credits exceeded markers", () => { @@ -25,6 +25,23 @@ describe("harness_retry_guard.cjs", () => { expect(result.awfAPIProxyBlockingRequests).toBe(false); }); + it("parses the AWF API proxy HTTP 403 AI credits rejection surfaced as an auth failure", () => { + const output = '{"text":"Failed to authenticate. API Error: 403 Maximum AI credits exceeded (302.111025 / 300)."} {"error":"authentication_failed"}'; + expect(detectNonRetryableHarnessGuard(output).aiCreditsExceeded).toBe(true); + expect(isAuthenticationFailedError(output)).toBe(true); + expect(parseAICreditsExceededProxyRejection(output)).toEqual({ aiCredits: 302.111025, maxAICredits: 300 }); + }); + + it("ignores AI credits markers that lack the proxy 403 usage pair", () => { + expect(parseAICreditsExceededProxyRejection("error: max_ai_credits_exceeded=true")).toBeNull(); + expect(parseAICreditsExceededProxyRejection("API Error: 403 Maximum AI credits exceeded")).toBeNull(); + expect(parseAICreditsExceededProxyRejection(undefined)).toBeNull(); + }); + + it("ignores a proxy 403 usage pair that has not reached the budget", () => { + expect(parseAICreditsExceededProxyRejection("API Error: 403 Maximum AI credits exceeded (12 / 300)")).toBeNull(); + }); + it("detects AWF API proxy blocking request markers", () => { const result = detectNonRetryableHarnessGuard("awf api proxy is blocking requests for this run"); expect(result.aiCreditsExceeded).toBe(false); From aef8bea465ed1dbd86832b982784028b2cc0e8a1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Aug 2026 03:09:16 +0000 Subject: [PATCH 3/6] Add boundary test for proxy 403 usage pair equal to budget Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/harness_retry_guard.test.cjs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/actions/setup/js/harness_retry_guard.test.cjs b/actions/setup/js/harness_retry_guard.test.cjs index 0ad8eac1eb5..49da09e734a 100644 --- a/actions/setup/js/harness_retry_guard.test.cjs +++ b/actions/setup/js/harness_retry_guard.test.cjs @@ -42,6 +42,10 @@ describe("harness_retry_guard.cjs", () => { expect(parseAICreditsExceededProxyRejection("API Error: 403 Maximum AI credits exceeded (12 / 300)")).toBeNull(); }); + it("accepts a proxy 403 usage pair that exactly reaches the budget", () => { + expect(parseAICreditsExceededProxyRejection("API Error: 403 Maximum AI credits exceeded (300 / 300)")).toEqual({ aiCredits: 300, maxAICredits: 300 }); + }); + it("detects AWF API proxy blocking request markers", () => { const result = detectNonRetryableHarnessGuard("awf api proxy is blocking requests for this run"); expect(result.aiCreditsExceeded).toBe(false); From d1963cf076a8b0ce279810fc1111c924e2e72f80 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Aug 2026 04:26:17 +0000 Subject: [PATCH 4/6] Require engine-authenticated JSON marker for proxy AI-credits rejection Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- actions/setup/js/harness_retry_guard.cjs | 43 +++++++++++++++---- actions/setup/js/harness_retry_guard.test.cjs | 30 +++++++++++-- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/actions/setup/js/harness_retry_guard.cjs b/actions/setup/js/harness_retry_guard.cjs index dbefeed522f..b962a25034e 100644 --- a/actions/setup/js/harness_retry_guard.cjs +++ b/actions/setup/js/harness_retry_guard.cjs @@ -58,20 +58,47 @@ function isAuthenticationFailedError(output) { /** * Extracts the AWF API proxy AI-credits budget rejection (HTTP 403) from harness output. - * Returns null when the output does not carry the proxy signature, or when the reported + * + * `output` is the combined stdout+stderr of the child process (see process_runner.cjs), + * which also carries verbatim assistant/model text. Matching the rejection text anywhere + * in that blob would let an unrelated assistant response that merely quotes or discusses + * this phrase (followed by any non-zero exit) masquerade as a trusted budget-abort signal. + * To require an engine-authenticated source, this only considers lines that parse as a + * standalone JSON object AND carry a structured API-error marker that only the harness' + * own transport layer sets — mirroring how `isInvalidRequestError` validates codex + * `turn.failed` events. Claude Code stamps the JSON event wrapping a proxy rejection with + * `is_api_error_message: true` and a string `error` field; plain conversational turns never + * set these. Free-form text (JSON-less lines, or JSON without either marker) is ignored. + * Returns null when no line carries the authenticated signature, or when the reported * usage does not actually reach the reported budget. * @param {unknown} output * @returns {{ aiCredits: number, maxAICredits: number } | null} */ function parseAICreditsExceededProxyRejection(output) { const safeOutput = typeof output === "string" ? output : ""; - const match = AI_CREDITS_EXCEEDED_PROXY_REJECTION_RE.exec(safeOutput); - if (!match) return null; - const aiCredits = Number.parseFloat(match[1]); - const maxAICredits = Number.parseFloat(match[2]); - if (!Number.isFinite(aiCredits) || !Number.isFinite(maxAICredits) || maxAICredits <= 0) return null; - if (aiCredits < maxAICredits) return null; - return { aiCredits, maxAICredits }; + for (const line of safeOutput.split(/\r?\n/)) { + const trimmed = line.trim(); + if (!trimmed || trimmed[0] !== "{") continue; + /** @type {unknown} */ + let parsed; + try { + parsed = JSON.parse(trimmed); + } catch { + continue; + } + if (!parsed || typeof parsed !== "object") continue; + const record = /** @type {Record} */ (parsed); + const isEngineFlaggedApiError = record.is_api_error_message === true || typeof record.error === "string"; + if (!isEngineFlaggedApiError) continue; + const match = AI_CREDITS_EXCEEDED_PROXY_REJECTION_RE.exec(JSON.stringify(record)); + if (!match) continue; + const aiCredits = Number.parseFloat(match[1]); + const maxAICredits = Number.parseFloat(match[2]); + if (!Number.isFinite(aiCredits) || !Number.isFinite(maxAICredits) || maxAICredits <= 0) continue; + if (aiCredits < maxAICredits) continue; + return { aiCredits, maxAICredits }; + } + return null; } /** diff --git a/actions/setup/js/harness_retry_guard.test.cjs b/actions/setup/js/harness_retry_guard.test.cjs index 49da09e734a..6dc6f8e76a9 100644 --- a/actions/setup/js/harness_retry_guard.test.cjs +++ b/actions/setup/js/harness_retry_guard.test.cjs @@ -26,7 +26,12 @@ describe("harness_retry_guard.cjs", () => { }); it("parses the AWF API proxy HTTP 403 AI credits rejection surfaced as an auth failure", () => { - const output = '{"text":"Failed to authenticate. API Error: 403 Maximum AI credits exceeded (302.111025 / 300)."} {"error":"authentication_failed"}'; + const output = JSON.stringify({ + type: "assistant", + message: { content: [{ type: "text", text: "Failed to authenticate. API Error: 403 Maximum AI credits exceeded (302.111025 / 300)." }] }, + error: "authentication_failed", + is_api_error_message: true, + }); expect(detectNonRetryableHarnessGuard(output).aiCreditsExceeded).toBe(true); expect(isAuthenticationFailedError(output)).toBe(true); expect(parseAICreditsExceededProxyRejection(output)).toEqual({ aiCredits: 302.111025, maxAICredits: 300 }); @@ -34,16 +39,33 @@ describe("harness_retry_guard.cjs", () => { it("ignores AI credits markers that lack the proxy 403 usage pair", () => { expect(parseAICreditsExceededProxyRejection("error: max_ai_credits_exceeded=true")).toBeNull(); - expect(parseAICreditsExceededProxyRejection("API Error: 403 Maximum AI credits exceeded")).toBeNull(); + expect(parseAICreditsExceededProxyRejection('{"error":"authentication_failed","message":"API Error: 403 Maximum AI credits exceeded"}')).toBeNull(); expect(parseAICreditsExceededProxyRejection(undefined)).toBeNull(); }); it("ignores a proxy 403 usage pair that has not reached the budget", () => { - expect(parseAICreditsExceededProxyRejection("API Error: 403 Maximum AI credits exceeded (12 / 300)")).toBeNull(); + expect(parseAICreditsExceededProxyRejection('{"error":"authentication_failed","message":"API Error: 403 Maximum AI credits exceeded (12 / 300)."}')).toBeNull(); }); it("accepts a proxy 403 usage pair that exactly reaches the budget", () => { - expect(parseAICreditsExceededProxyRejection("API Error: 403 Maximum AI credits exceeded (300 / 300)")).toEqual({ aiCredits: 300, maxAICredits: 300 }); + expect(parseAICreditsExceededProxyRejection('{"error":"authentication_failed","message":"API Error: 403 Maximum AI credits exceeded (300 / 300)."}')).toEqual({ + aiCredits: 300, + maxAICredits: 300, + }); + }); + + it("ignores the proxy 403 usage pair when it only appears in plain assistant text without an engine error marker", () => { + // Simulates an assistant response that merely discusses/quotes the phrase — this must + // NOT be trusted as proxy evidence since there is no engine-authenticated error marker. + const output = JSON.stringify({ + type: "assistant", + message: { content: [{ type: "text", text: "Earlier the run hit: API Error: 403 Maximum AI credits exceeded (302.111025 / 300)." }] }, + }); + expect(parseAICreditsExceededProxyRejection(output)).toBeNull(); + }); + + it("ignores JSON lines without an engine error marker even when the usage pair is present", () => { + expect(parseAICreditsExceededProxyRejection('{"type":"assistant","text":"API Error: 403 Maximum AI credits exceeded (302.111025 / 300)."}')).toBeNull(); }); it("detects AWF API proxy blocking request markers", () => { From a4cb063abb5ec9489ff4b881217f930d2852de5f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Aug 2026 04:28:37 +0000 Subject: [PATCH 5/6] Add codex/copilot proxy-403 regression tests and document safeOutput contract Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- actions/setup/js/codex_harness.test.cjs | 35 ++++++++++++++++++++++ actions/setup/js/copilot_harness.test.cjs | 36 +++++++++++++++++++++++ actions/setup/js/harness_retry_guard.cjs | 4 +++ 3 files changed, 75 insertions(+) diff --git a/actions/setup/js/codex_harness.test.cjs b/actions/setup/js/codex_harness.test.cjs index 6033ec0321b..b495f898e7c 100644 --- a/actions/setup/js/codex_harness.test.cjs +++ b/actions/setup/js/codex_harness.test.cjs @@ -567,6 +567,41 @@ process.exit(1);`, expect(result.stderr).toContain("invalid_request_error (HTTP 400) — not retrying"); }); + it("exits 0 when the AWF API proxy returns HTTP 403 max-AI-credits as an authentication failure", () => { + // Same proxy signature as the claude_harness.test.cjs regression, replayed against codex. + // CODEX_API_KEY is set so the `!isMissingApiKey` guard cannot suppress the budget path. + const tempDir = makeHarnessTempDir("codex-ai-credits-proxy-403-"); + const stubPath = path.join(tempDir, "stub.cjs"); + const promptPath = path.join(tempDir, "prompt.txt"); + const callsPath = path.join(tempDir, "calls.jsonl"); + fs.writeFileSync( + stubPath, + `const fs = require("fs"); +fs.appendFileSync(process.env.CODEX_HARNESS_STUB_CALLS, "called\\n"); +process.stdout.write(JSON.stringify({ type: "error", error: "authentication_failed", message: "Failed to authenticate. API Error: 403 Maximum AI credits exceeded (302.111025 / 300)." }) + "\\n"); +process.exit(1);`, + "utf8" + ); + fs.writeFileSync(promptPath, "fix the bug", "utf8"); + + const result = spawnSync(process.execPath, ["codex_harness.cjs", process.execPath, stubPath, "exec", "--prompt-file", promptPath], { + cwd: path.dirname(require.resolve("./codex_harness.cjs")), + env: { + ...process.env, + CODEX_HARNESS_STUB_CALLS: callsPath, + CODEX_API_KEY: "fake-key-for-test", + GH_AW_HARNESS_MAX_RETRIES: "0", + }, + encoding: "utf8", + timeout: 10000, + }); + + expect(fs.readFileSync(callsPath, "utf8").trim().split("\n")).toHaveLength(1); + expect(result.status).toBe(0); + expect(result.stderr).toContain("trusted budget-abort evidence"); + expect(result.stderr).toContain("AI credits budget enforced"); + }); + it("retries on rate limit error even without output", () => { const result = { exitCode: 1, hasOutput: false, output: "rate_limit_exceeded" }; expect(shouldRetry(result, 0)).toBe(true); diff --git a/actions/setup/js/copilot_harness.test.cjs b/actions/setup/js/copilot_harness.test.cjs index e0a45b2ca0c..3d20dd50e7f 100644 --- a/actions/setup/js/copilot_harness.test.cjs +++ b/actions/setup/js/copilot_harness.test.cjs @@ -3086,6 +3086,42 @@ process.exit(1);`, expect(result.stderr).toContain("AI credits budget enforced"); }); + it("exits 0 when the AWF API proxy returns HTTP 403 max-AI-credits as an authentication failure", () => { + // Same proxy signature as the claude_harness.test.cjs regression, replayed against copilot. + const tempDir = makeHarnessTempDir("copilot-ai-credits-proxy-403-"); + const safeOutputsPath = path.join(tempDir, "safe-outputs.jsonl"); + const stubPath = path.join(tempDir, "stub.cjs"); + const promptPath = path.join(tempDir, "prompt.txt"); + const callsPath = path.join(tempDir, "calls.jsonl"); + fs.writeFileSync( + stubPath, + `const fs = require("fs"); +const callsPath = process.env.COPILOT_HARNESS_STUB_CALLS; +fs.appendFileSync(callsPath, JSON.stringify({args: process.argv.slice(2)}) + "\\n"); +process.stdout.write(JSON.stringify({error: "authentication_failed", message: "Failed to authenticate. API Error: 403 Maximum AI credits exceeded (302.111025 / 300)."}) + "\\n"); +process.exit(1);`, + "utf8" + ); + fs.writeFileSync(promptPath, "do some work", "utf8"); + + const result = spawnSync(process.execPath, ["copilot_harness.cjs", process.execPath, stubPath, "--prompt-file", promptPath], { + cwd: path.dirname(require.resolve("./copilot_harness.cjs")), + env: { + ...process.env, + COPILOT_HARNESS_STUB_CALLS: callsPath, + GH_AW_SAFE_OUTPUTS: safeOutputsPath, + GH_AW_HARNESS_MAX_RETRIES: "0", + }, + encoding: "utf8", + timeout: 10000, + }); + const callCount = fs.readFileSync(callsPath, "utf8").trim().split("\n").filter(Boolean).length; + expect(callCount).toBe(1); + expect(result.status).toBe(0); + expect(result.stderr).toContain("trusted budget-abort evidence"); + expect(result.stderr).toContain("AI credits budget enforced"); + }); + it("keeps non-zero exit for auth failure even when AI-credit markers and trusted audit are present", () => { const tempDir = makeHarnessTempDir("copilot-auth-failure-"); const safeOutputsPath = path.join(tempDir, "safe-outputs.jsonl"); diff --git a/actions/setup/js/harness_retry_guard.cjs b/actions/setup/js/harness_retry_guard.cjs index b962a25034e..fe43c09f4d5 100644 --- a/actions/setup/js/harness_retry_guard.cjs +++ b/actions/setup/js/harness_retry_guard.cjs @@ -75,6 +75,10 @@ function isAuthenticationFailedError(output) { * @returns {{ aiCredits: number, maxAICredits: number } | null} */ function parseAICreditsExceededProxyRejection(output) { + // Contract: callers must pass the joined stdout+stderr string (`result.output`, as built by + // process_runner.cjs), matching every other guard in this file. Non-string input (e.g. an + // array/object of structured log lines) is treated as "no output" rather than coerced, since + // `String(someObject)` would produce a meaningless "[object Object]"-style value. const safeOutput = typeof output === "string" ? output : ""; for (const line of safeOutput.split(/\r?\n/)) { const trimmed = line.trim(); From d6394a0c6b7dd0a3afcbda150f40b1b9a8a7e1d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Aug 2026 17:27:25 +0000 Subject: [PATCH 6/6] Fix prettier formatting in harness_retry_guard.cjs Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/smoke-claude.lock.yml | 6 +++++- .github/workflows/step-name-alignment.lock.yml | 6 +++++- actions/setup/js/harness_retry_guard.cjs | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index 5df0a0ba4ad..bd609de57f0 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -1549,6 +1549,7 @@ jobs: # - Bash # - BashOutput # - Edit + # - Edit(/tmp/*) # - Edit(/tmp/gh-aw/agent/*) # - Edit(/tmp/gh-aw/cache-memory/*) # - ExitPlanMode @@ -1557,16 +1558,19 @@ jobs: # - KillBash # - LS # - MultiEdit + # - MultiEdit(/tmp/*) # - MultiEdit(/tmp/gh-aw/agent/*) # - MultiEdit(/tmp/gh-aw/cache-memory/*) # - NotebookEdit # - NotebookRead # - Read + # - Read(/tmp/*) # - Read(/tmp/gh-aw/agent/*) # - Read(/tmp/gh-aw/cache-memory/*) # - Task # - TodoWrite # - Write + # - Write(/tmp/*) # - Write(/tmp/gh-aw/agent/*) # - Write(/tmp/gh-aw/cache-memory/*) # - mcp__agenticworkflows @@ -1667,7 +1671,7 @@ jobs: fi # shellcheck disable=SC1003,SC2016,SC2086 awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --tty --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env ANTHROPIC_API_KEY --exclude-env GH_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --exclude-env TAVILY_API_KEY --mount /tmp/gh-aw:/tmp/gh-aw:rw --log-level info --skip-pull --difc-proxy-host awmg-cli-proxy:18443 --difc-proxy-ca-cert /tmp/gh-aw/difc-proxy-tls/ca.crt \ - -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/claude_harness.cjs claude --print --no-chrome --max-turns 100 --allowed-tools '\''Bash,BashOutput,Edit,Edit(/tmp/gh-aw/agent/*),Edit(/tmp/gh-aw/cache-memory/*),ExitPlanMode,Glob,Grep,KillBash,LS,MultiEdit,MultiEdit(/tmp/gh-aw/agent/*),MultiEdit(/tmp/gh-aw/cache-memory/*),NotebookEdit,NotebookRead,Read,Read(/tmp/gh-aw/agent/*),Read(/tmp/gh-aw/cache-memory/*),Task,TodoWrite,Write,Write(/tmp/gh-aw/agent/*),Write(/tmp/gh-aw/cache-memory/*),mcp__agenticworkflows,mcp__github__actions_get,mcp__github__actions_list,mcp__github__get_code_scanning_alert,mcp__github__get_commit,mcp__github__get_dependabot_alert,mcp__github__get_discussion,mcp__github__get_discussion_comments,mcp__github__get_file_contents,mcp__github__get_job_logs,mcp__github__get_label,mcp__github__get_latest_release,mcp__github__get_me,mcp__github__get_notification_details,mcp__github__get_pull_request,mcp__github__get_pull_request_comments,mcp__github__get_pull_request_diff,mcp__github__get_pull_request_files,mcp__github__get_pull_request_review_comments,mcp__github__get_pull_request_reviews,mcp__github__get_pull_request_status,mcp__github__get_release_by_tag,mcp__github__get_secret_scanning_alert,mcp__github__get_tag,mcp__github__issue_read,mcp__github__list_branches,mcp__github__list_code_scanning_alerts,mcp__github__list_commits,mcp__github__list_dependabot_alerts,mcp__github__list_discussion_categories,mcp__github__list_discussions,mcp__github__list_issue_types,mcp__github__list_issues,mcp__github__list_label,mcp__github__list_notifications,mcp__github__list_pull_requests,mcp__github__list_releases,mcp__github__list_secret_scanning_alerts,mcp__github__list_starred_repositories,mcp__github__list_tags,mcp__github__pull_request_read,mcp__github__search_code,mcp__github__search_issues,mcp__github__search_orgs,mcp__github__search_pull_requests,mcp__github__search_repositories,mcp__github__search_users,mcp__mcpscripts,mcp__playwright__browser_click,mcp__playwright__browser_close,mcp__playwright__browser_console_messages,mcp__playwright__browser_drag,mcp__playwright__browser_evaluate,mcp__playwright__browser_file_upload,mcp__playwright__browser_fill_form,mcp__playwright__browser_handle_dialog,mcp__playwright__browser_hover,mcp__playwright__browser_install,mcp__playwright__browser_navigate,mcp__playwright__browser_navigate_back,mcp__playwright__browser_network_requests,mcp__playwright__browser_press_key,mcp__playwright__browser_resize,mcp__playwright__browser_select_option,mcp__playwright__browser_snapshot,mcp__playwright__browser_tabs,mcp__playwright__browser_take_screenshot,mcp__playwright__browser_type,mcp__playwright__browser_wait_for,mcp__safeoutputs,mcp__tavily'\'' --debug-file /tmp/gh-aw/agent/claude-debug.log --verbose --permission-mode acceptEdits --output-format stream-json --bare --mcp-config "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/claude_harness.cjs claude --print --no-chrome --max-turns 100 --allowed-tools '\''Bash,BashOutput,Edit,Edit(/tmp/*),Edit(/tmp/gh-aw/agent/*),Edit(/tmp/gh-aw/cache-memory/*),ExitPlanMode,Glob,Grep,KillBash,LS,MultiEdit,MultiEdit(/tmp/*),MultiEdit(/tmp/gh-aw/agent/*),MultiEdit(/tmp/gh-aw/cache-memory/*),NotebookEdit,NotebookRead,Read,Read(/tmp/*),Read(/tmp/gh-aw/agent/*),Read(/tmp/gh-aw/cache-memory/*),Task,TodoWrite,Write,Write(/tmp/*),Write(/tmp/gh-aw/agent/*),Write(/tmp/gh-aw/cache-memory/*),mcp__agenticworkflows,mcp__github__actions_get,mcp__github__actions_list,mcp__github__get_code_scanning_alert,mcp__github__get_commit,mcp__github__get_dependabot_alert,mcp__github__get_discussion,mcp__github__get_discussion_comments,mcp__github__get_file_contents,mcp__github__get_job_logs,mcp__github__get_label,mcp__github__get_latest_release,mcp__github__get_me,mcp__github__get_notification_details,mcp__github__get_pull_request,mcp__github__get_pull_request_comments,mcp__github__get_pull_request_diff,mcp__github__get_pull_request_files,mcp__github__get_pull_request_review_comments,mcp__github__get_pull_request_reviews,mcp__github__get_pull_request_status,mcp__github__get_release_by_tag,mcp__github__get_secret_scanning_alert,mcp__github__get_tag,mcp__github__issue_read,mcp__github__list_branches,mcp__github__list_code_scanning_alerts,mcp__github__list_commits,mcp__github__list_dependabot_alerts,mcp__github__list_discussion_categories,mcp__github__list_discussions,mcp__github__list_issue_types,mcp__github__list_issues,mcp__github__list_label,mcp__github__list_notifications,mcp__github__list_pull_requests,mcp__github__list_releases,mcp__github__list_secret_scanning_alerts,mcp__github__list_starred_repositories,mcp__github__list_tags,mcp__github__pull_request_read,mcp__github__search_code,mcp__github__search_issues,mcp__github__search_orgs,mcp__github__search_pull_requests,mcp__github__search_repositories,mcp__github__search_users,mcp__mcpscripts,mcp__playwright__browser_click,mcp__playwright__browser_close,mcp__playwright__browser_console_messages,mcp__playwright__browser_drag,mcp__playwright__browser_evaluate,mcp__playwright__browser_file_upload,mcp__playwright__browser_fill_form,mcp__playwright__browser_handle_dialog,mcp__playwright__browser_hover,mcp__playwright__browser_install,mcp__playwright__browser_navigate,mcp__playwright__browser_navigate_back,mcp__playwright__browser_network_requests,mcp__playwright__browser_press_key,mcp__playwright__browser_resize,mcp__playwright__browser_select_option,mcp__playwright__browser_snapshot,mcp__playwright__browser_tabs,mcp__playwright__browser_take_screenshot,mcp__playwright__browser_type,mcp__playwright__browser_wait_for,mcp__safeoutputs,mcp__tavily'\'' --debug-file /tmp/gh-aw/agent/claude-debug.log --verbose --permission-mode acceptEdits --output-format stream-json --bare --mcp-config "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} ANTHROPIC_MAX_RETRIES: 0 diff --git a/.github/workflows/step-name-alignment.lock.yml b/.github/workflows/step-name-alignment.lock.yml index 79ea464dff2..621b8c694a6 100644 --- a/.github/workflows/step-name-alignment.lock.yml +++ b/.github/workflows/step-name-alignment.lock.yml @@ -871,6 +871,7 @@ jobs: # - Bash(yq*) # - BashOutput # - Edit + # - Edit(/tmp/*) # - Edit(/tmp/gh-aw/agent/*) # - Edit(/tmp/gh-aw/cache-memory/*) # - ExitPlanMode @@ -879,16 +880,19 @@ jobs: # - KillBash # - LS # - MultiEdit + # - MultiEdit(/tmp/*) # - MultiEdit(/tmp/gh-aw/agent/*) # - MultiEdit(/tmp/gh-aw/cache-memory/*) # - NotebookEdit # - NotebookRead # - Read + # - Read(/tmp/*) # - Read(/tmp/gh-aw/agent/*) # - Read(/tmp/gh-aw/cache-memory/*) # - Task # - TodoWrite # - Write + # - Write(/tmp/*) # - Write(/tmp/gh-aw/agent/*) # - Write(/tmp/gh-aw/cache-memory/*) # - mcp__github__actions_get @@ -965,7 +969,7 @@ jobs: fi # shellcheck disable=SC1003,SC2016,SC2086 awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --tty --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env ANTHROPIC_API_KEY --exclude-env GH_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --mount /tmp/gh-aw:/tmp/gh-aw:rw --log-level info --skip-pull --difc-proxy-host awmg-cli-proxy:18443 --difc-proxy-ca-cert /tmp/gh-aw/difc-proxy-tls/ca.crt \ - -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/claude_harness.cjs claude --print --no-chrome --max-turns 50 --allowed-tools '\''Bash(cat /tmp/gh-aw/agent/step-alignment-input.json),Bash(cat /tmp/gh-aw/cache-memory/),Bash(cat > /tmp/gh-aw/cache-memory/),Bash(cat docs/src/content/docs/reference/glossary.md),Bash(cat),Bash(date),Bash(echo),Bash(find .github/workflows -name "*.lock.yml" -type f),Bash(gh:*),Bash(git log --since="24 hours ago" --oneline --name-only -- ".github/workflows/*.lock.yml"),Bash(grep),Bash(head),Bash(jq* /tmp/gh-aw/agent/step-alignment-input.json),Bash(ls),Bash(mkdir -p /tmp/gh-aw/cache-memory/),Bash(mv /tmp/gh-aw/cache-memory/),Bash(printf),Bash(pwd),Bash(safeoutputs:*),Bash(sort),Bash(tail),Bash(uniq),Bash(wc),Bash(yq),Bash(yq*),BashOutput,Edit,Edit(/tmp/gh-aw/agent/*),Edit(/tmp/gh-aw/cache-memory/*),ExitPlanMode,Glob,Grep,KillBash,LS,MultiEdit,MultiEdit(/tmp/gh-aw/agent/*),MultiEdit(/tmp/gh-aw/cache-memory/*),NotebookEdit,NotebookRead,Read,Read(/tmp/gh-aw/agent/*),Read(/tmp/gh-aw/cache-memory/*),Task,TodoWrite,Write,Write(/tmp/gh-aw/agent/*),Write(/tmp/gh-aw/cache-memory/*),mcp__github__actions_get,mcp__github__actions_list,mcp__github__get_code_scanning_alert,mcp__github__get_commit,mcp__github__get_dependabot_alert,mcp__github__get_discussion,mcp__github__get_discussion_comments,mcp__github__get_file_contents,mcp__github__get_job_logs,mcp__github__get_label,mcp__github__get_latest_release,mcp__github__get_me,mcp__github__get_notification_details,mcp__github__get_pull_request,mcp__github__get_pull_request_comments,mcp__github__get_pull_request_diff,mcp__github__get_pull_request_files,mcp__github__get_pull_request_review_comments,mcp__github__get_pull_request_reviews,mcp__github__get_pull_request_status,mcp__github__get_release_by_tag,mcp__github__get_secret_scanning_alert,mcp__github__get_tag,mcp__github__issue_read,mcp__github__list_branches,mcp__github__list_code_scanning_alerts,mcp__github__list_commits,mcp__github__list_dependabot_alerts,mcp__github__list_discussion_categories,mcp__github__list_discussions,mcp__github__list_issue_types,mcp__github__list_issues,mcp__github__list_label,mcp__github__list_notifications,mcp__github__list_pull_requests,mcp__github__list_releases,mcp__github__list_secret_scanning_alerts,mcp__github__list_starred_repositories,mcp__github__list_tags,mcp__github__pull_request_read,mcp__github__search_code,mcp__github__search_issues,mcp__github__search_orgs,mcp__github__search_pull_requests,mcp__github__search_repositories,mcp__github__search_users,mcp__safeoutputs'\'' --debug-file /tmp/gh-aw/agent/claude-debug.log --verbose --permission-mode acceptEdits --output-format stream-json --mcp-config "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt${GH_AW_MODEL_AGENT_CLAUDE:+ --model "$GH_AW_MODEL_AGENT_CLAUDE"}' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/claude_harness.cjs claude --print --no-chrome --max-turns 50 --allowed-tools '\''Bash(cat /tmp/gh-aw/agent/step-alignment-input.json),Bash(cat /tmp/gh-aw/cache-memory/),Bash(cat > /tmp/gh-aw/cache-memory/),Bash(cat docs/src/content/docs/reference/glossary.md),Bash(cat),Bash(date),Bash(echo),Bash(find .github/workflows -name "*.lock.yml" -type f),Bash(gh:*),Bash(git log --since="24 hours ago" --oneline --name-only -- ".github/workflows/*.lock.yml"),Bash(grep),Bash(head),Bash(jq* /tmp/gh-aw/agent/step-alignment-input.json),Bash(ls),Bash(mkdir -p /tmp/gh-aw/cache-memory/),Bash(mv /tmp/gh-aw/cache-memory/),Bash(printf),Bash(pwd),Bash(safeoutputs:*),Bash(sort),Bash(tail),Bash(uniq),Bash(wc),Bash(yq),Bash(yq*),BashOutput,Edit,Edit(/tmp/*),Edit(/tmp/gh-aw/agent/*),Edit(/tmp/gh-aw/cache-memory/*),ExitPlanMode,Glob,Grep,KillBash,LS,MultiEdit,MultiEdit(/tmp/*),MultiEdit(/tmp/gh-aw/agent/*),MultiEdit(/tmp/gh-aw/cache-memory/*),NotebookEdit,NotebookRead,Read,Read(/tmp/*),Read(/tmp/gh-aw/agent/*),Read(/tmp/gh-aw/cache-memory/*),Task,TodoWrite,Write,Write(/tmp/*),Write(/tmp/gh-aw/agent/*),Write(/tmp/gh-aw/cache-memory/*),mcp__github__actions_get,mcp__github__actions_list,mcp__github__get_code_scanning_alert,mcp__github__get_commit,mcp__github__get_dependabot_alert,mcp__github__get_discussion,mcp__github__get_discussion_comments,mcp__github__get_file_contents,mcp__github__get_job_logs,mcp__github__get_label,mcp__github__get_latest_release,mcp__github__get_me,mcp__github__get_notification_details,mcp__github__get_pull_request,mcp__github__get_pull_request_comments,mcp__github__get_pull_request_diff,mcp__github__get_pull_request_files,mcp__github__get_pull_request_review_comments,mcp__github__get_pull_request_reviews,mcp__github__get_pull_request_status,mcp__github__get_release_by_tag,mcp__github__get_secret_scanning_alert,mcp__github__get_tag,mcp__github__issue_read,mcp__github__list_branches,mcp__github__list_code_scanning_alerts,mcp__github__list_commits,mcp__github__list_dependabot_alerts,mcp__github__list_discussion_categories,mcp__github__list_discussions,mcp__github__list_issue_types,mcp__github__list_issues,mcp__github__list_label,mcp__github__list_notifications,mcp__github__list_pull_requests,mcp__github__list_releases,mcp__github__list_secret_scanning_alerts,mcp__github__list_starred_repositories,mcp__github__list_tags,mcp__github__pull_request_read,mcp__github__search_code,mcp__github__search_issues,mcp__github__search_orgs,mcp__github__search_pull_requests,mcp__github__search_repositories,mcp__github__search_users,mcp__safeoutputs'\'' --debug-file /tmp/gh-aw/agent/claude-debug.log --verbose --permission-mode acceptEdits --output-format stream-json --mcp-config "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt${GH_AW_MODEL_AGENT_CLAUDE:+ --model "$GH_AW_MODEL_AGENT_CLAUDE"}' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} ANTHROPIC_MAX_RETRIES: 0 diff --git a/actions/setup/js/harness_retry_guard.cjs b/actions/setup/js/harness_retry_guard.cjs index fe43c09f4d5..ea3cf71b63e 100644 --- a/actions/setup/js/harness_retry_guard.cjs +++ b/actions/setup/js/harness_retry_guard.cjs @@ -91,6 +91,7 @@ function parseAICreditsExceededProxyRejection(output) { continue; } if (!parsed || typeof parsed !== "object") continue; + // prettier-ignore const record = /** @type {Record} */ (parsed); const isEngineFlaggedApiError = record.is_api_error_message === true || typeof record.error === "string"; if (!isEngineFlaggedApiError) continue;