From e6588d1b4f5ad2b4cf38ef145b96d6a3ad37828d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Aug 2026 13:44:29 +0000 Subject: [PATCH 1/4] Initial plan From 0b46db044066b73864abe1f6b74023139e5320dd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Aug 2026 13:56:10 +0000 Subject: [PATCH 2/4] Rescue watchdog-fired auth false failures Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/copilot_harness.cjs | 6 +++- actions/setup/js/copilot_harness.test.cjs | 42 +++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/actions/setup/js/copilot_harness.cjs b/actions/setup/js/copilot_harness.cjs index 4b08b41ff2c..b4ba375384d 100644 --- a/actions/setup/js/copilot_harness.cjs +++ b/actions/setup/js/copilot_harness.cjs @@ -1272,7 +1272,11 @@ async function main() { // only armed after hasTerminalSafeOutput is true, so watchdogFired on a no-stdio-output // run means the agent completed its task (wrote safe-output) but produced no console // output before the watchdog terminated the idle process. - if ((failureClass === "partial_execution" || failureClass === "long_run_exit" || (failureClass === "no_output" && result.watchdogFired)) && safeOutputsPath && hasTerminalSafeOutput(safeOutputsPath)) { + if ( + (failureClass === "partial_execution" || failureClass === "long_run_exit" || (failureClass === "no_output" && result.watchdogFired) || (failureClass === "authentication_failed" && result.watchdogFired)) && + safeOutputsPath && + hasTerminalSafeOutput(safeOutputsPath) + ) { const reason = result.watchdogFired ? "post-result watchdog fired after terminal safe-output was emitted" : "partial execution after terminal safe-output was already produced"; log(`attempt ${attempt + 1}: ${reason} — treating as success (late-activity exit suppressed)`); lastExitCode = 0; diff --git a/actions/setup/js/copilot_harness.test.cjs b/actions/setup/js/copilot_harness.test.cjs index fbe387f4b7b..79b644bbf4f 100644 --- a/actions/setup/js/copilot_harness.test.cjs +++ b/actions/setup/js/copilot_harness.test.cjs @@ -2620,6 +2620,48 @@ setInterval(() => {}, 1000);`, expect(result.stderr).toContain("post-result watchdog fired after terminal safe-output was emitted"); expect(result.stderr).toContain("late-activity exit suppressed"); }); + + it('exits 0 without retrying when watchdog fires after terminal safe-output was produced and output contains benign "not logged in" tool text', () => { + const tempDir = makeHarnessTempDir("copilot-watchdog-authentication-failed-suppression-"); + 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; +const safeOutputsPath = process.env.GH_AW_SAFE_OUTPUTS; +fs.appendFileSync(callsPath, JSON.stringify({args: process.argv.slice(2)}) + "\\n"); +fs.appendFileSync(safeOutputsPath, JSON.stringify({type:"add_comment",body:"Daily report posted"}) + "\\n"); +process.stdout.write(JSON.stringify({ + type: "tool.execution_complete", + tool: "bash", + output: "You are not logged into any GitHub hosts. To log in, run: gh auth login" +}) + "\\n"); +process.on("SIGTERM", () => process.exit(1)); +setInterval(() => {}, 1000);`, + "utf8" + ); + fs.writeFileSync(promptPath, "generate the report", "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_WATCHDOG_TIMEOUT_MS: "100", + }, + encoding: "utf8", + timeout: 15000, + }); + 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("post-result watchdog fired after terminal safe-output was emitted"); + expect(result.stderr).toContain("late-activity exit suppressed"); + }); }); describe("AI credits budget enforcement exits 0", () => { From 290ef817da32c4cfe53b60d0565279653b0a89e9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Aug 2026 15:20:58 +0000 Subject: [PATCH 3/4] Address review feedback: extract isExpectedLateExit, add negative auth test, fix stale contract test Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- actions/setup/js/copilot_harness.cjs | 7 ++-- actions/setup/js/copilot_harness.test.cjs | 35 +++++++++++++++++++ ...quality_reviewer_workflow_contract_test.go | 2 +- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/actions/setup/js/copilot_harness.cjs b/actions/setup/js/copilot_harness.cjs index b4ba375384d..688471f14de 100644 --- a/actions/setup/js/copilot_harness.cjs +++ b/actions/setup/js/copilot_harness.cjs @@ -1272,11 +1272,8 @@ async function main() { // only armed after hasTerminalSafeOutput is true, so watchdogFired on a no-stdio-output // run means the agent completed its task (wrote safe-output) but produced no console // output before the watchdog terminated the idle process. - if ( - (failureClass === "partial_execution" || failureClass === "long_run_exit" || (failureClass === "no_output" && result.watchdogFired) || (failureClass === "authentication_failed" && result.watchdogFired)) && - safeOutputsPath && - hasTerminalSafeOutput(safeOutputsPath) - ) { + const isExpectedLateExit = failureClass === "partial_execution" || failureClass === "long_run_exit" || (failureClass === "no_output" && result.watchdogFired) || (failureClass === "authentication_failed" && result.watchdogFired); + if (isExpectedLateExit && safeOutputsPath && hasTerminalSafeOutput(safeOutputsPath)) { const reason = result.watchdogFired ? "post-result watchdog fired after terminal safe-output was emitted" : "partial execution after terminal safe-output was already produced"; log(`attempt ${attempt + 1}: ${reason} — treating as success (late-activity exit suppressed)`); lastExitCode = 0; diff --git a/actions/setup/js/copilot_harness.test.cjs b/actions/setup/js/copilot_harness.test.cjs index 79b644bbf4f..5a26d10478f 100644 --- a/actions/setup/js/copilot_harness.test.cjs +++ b/actions/setup/js/copilot_harness.test.cjs @@ -2662,6 +2662,41 @@ setInterval(() => {}, 1000);`, expect(result.stderr).toContain("post-result watchdog fired after terminal safe-output was emitted"); expect(result.stderr).toContain("late-activity exit suppressed"); }); + + it("does not rescue authentication_failed when no terminal safe-output was produced before the watchdog fires", () => { + const tempDir = makeHarnessTempDir("copilot-watchdog-auth-failed-no-output-"); + 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"); + // Stub emits auth-failure-looking text but does NOT write any safe-output entry. + // Without terminal safe-output the watchdog never arms, so authentication_failed + // falls through to the normal non-retryable failure path and exits non-zero. + 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("Error: No authentication information found.\\n"); +process.exit(1);`, + "utf8" + ); + fs.writeFileSync(promptPath, "generate the report", "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, + }, + encoding: "utf8", + timeout: 15000, + }); + // Harness exits non-zero: genuine auth failure with no terminal safe-output is not rescued + expect(result.status).not.toBe(0); + expect(result.stderr).not.toContain("late-activity exit suppressed"); + }); }); describe("AI credits budget enforcement exits 0", () => { diff --git a/pkg/cli/pr_code_quality_reviewer_workflow_contract_test.go b/pkg/cli/pr_code_quality_reviewer_workflow_contract_test.go index fbad02eb033..62f26649166 100644 --- a/pkg/cli/pr_code_quality_reviewer_workflow_contract_test.go +++ b/pkg/cli/pr_code_quality_reviewer_workflow_contract_test.go @@ -24,6 +24,6 @@ func TestPRCodeQualityReviewerWorkflowSubAgentModelContract(t *testing.T) { text := string(content) assert.Contains(t, text, "## agent: `grumpy-coder`", "Workflow should define the grumpy-coder sub-agent") - assert.Contains(t, text, "model: claude-haiku-4.5", "Sub-agent should pin a supported Haiku model") + assert.Contains(t, text, "model: small", "Sub-agent should use the portable small alias") assert.NotContains(t, text, "model: inherited", "Sub-agent should not inherit an unsupported tier-specific model") } From efc739d0306d60265ad7c86712f8518e9130e0f7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 2 Aug 2026 15:58:00 +0000 Subject: [PATCH 4/4] fix(codex-harness): gate fetchAWFReflect on AWF_REFLECT_ENABLED to prevent test timeouts Adds the same AWF_REFLECT_ENABLED guard used in copilot_harness.cjs around both fetchAWFReflect calls in codex_harness.cjs. Without this guard, the codex harness unconditionally makes a network request to api-proxy with a 60-second timeout. In CI environments where api-proxy is accessible but slow, spawnSync tests with 10-second timeouts would be killed (returning null status) rather than completing cleanly. Also removes the unused AWF_REFLECT_TIMEOUT_MS dead import." Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/codex_harness.cjs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/actions/setup/js/codex_harness.cjs b/actions/setup/js/codex_harness.cjs index 9187a81a6f1..bfe086c9450 100644 --- a/actions/setup/js/codex_harness.cjs +++ b/actions/setup/js/codex_harness.cjs @@ -39,7 +39,6 @@ const { runProcess, formatDuration, sleep, MIN_POST_RESULT_WATCHDOG_TIMEOUT_MS, const { AWF_API_PROXY_REFLECT_URL, AWF_REFLECT_OUTPUT_PATH, - AWF_REFLECT_TIMEOUT_MS, AWF_MODELS_URL_TIMEOUT_MS, GEMINI_MODEL_NAME_PREFIX, enrichReflectModels, @@ -536,7 +535,10 @@ async function main() { // Fetch AWF API proxy reflection data before running the agent to capture initial proxy state. // This is best-effort: failures are logged but do not affect the agent run. - await fetchAWFReflect({ logger: log }); + // Skip when AWF_REFLECT_ENABLED is not "1" (e.g. no api-proxy running in sandbox or test mode). + if (process.env.AWF_REFLECT_ENABLED === "1") { + await fetchAWFReflect({ logger: log }); + } const codexHome = process.env.CODEX_HOME || ""; let codexEnv = codexChildEnv; const providerConfig = configureCodexProviderFromReflect({ @@ -752,7 +754,10 @@ async function main() { } // Fetch AWF API proxy reflection data and persist to disk for post-run step summary. - await fetchAWFReflect({ logger: log }); + // Skip when AWF_REFLECT_ENABLED is not "1" (e.g. no api-proxy running in sandbox or test mode). + if (process.env.AWF_REFLECT_ENABLED === "1") { + await fetchAWFReflect({ logger: log }); + } log(`done: exitCode=${lastExitCode} totalDuration=${formatDuration(Date.now() - driverStartTime)}`); process.exit(lastExitCode);