From b72b9df04b2803bd1e7fb59e28d526eda827735b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 22:30:33 +0000 Subject: [PATCH 1/2] feat: extract agent timeout text to markdown template file Move the hardcoded timeout message from buildTimeoutContext() in handle_agent_failure.cjs into actions/setup/md/agent_timeout.md. The new template uses {current_minutes} and {suggested_minutes} placeholders rendered via renderTemplate(), following the same pattern used by buildInferenceAccessErrorContext() and buildLockdownCheckFailedContext(). The setup.sh script already copies all md/*.md files to ${RUNNER_TEMP}/gh-aw/prompts/ at runtime, so the file is automatically available to the conclusion job. Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> Agent-Logs-Url: https://github.com/github/gh-aw/sessions/2718c9d9-d8d3-4899-8818-0818d6bde90a --- actions/setup/js/handle_agent_failure.cjs | 13 +++---------- actions/setup/md/agent_timeout.md | 9 +++++++++ 2 files changed, 12 insertions(+), 10 deletions(-) create mode 100644 actions/setup/md/agent_timeout.md diff --git a/actions/setup/js/handle_agent_failure.cjs b/actions/setup/js/handle_agent_failure.cjs index ed5797d727..30116d4f2b 100644 --- a/actions/setup/js/handle_agent_failure.cjs +++ b/actions/setup/js/handle_agent_failure.cjs @@ -608,16 +608,9 @@ function buildTimeoutContext(isTimedOut, timeoutMinutes) { const currentMinutes = parseInt(timeoutMinutes || "20", 10); const suggestedMinutes = currentMinutes + 10; - let ctx = "\n**⏱️ Agent Timed Out**: The agent job exceeded the maximum allowed execution time"; - ctx += ` (${currentMinutes} minutes).`; - ctx += "\n\nTo increase the timeout, add or update the `timeout-minutes` setting in your workflow's frontmatter:\n\n"; - ctx += "```yaml\n"; - ctx += "---\n"; - ctx += `timeout-minutes: ${suggestedMinutes}\n`; - ctx += "---\n"; - ctx += "```\n\n"; - - return ctx; + const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/agent_timeout.md`; + const template = fs.readFileSync(templatePath, "utf8"); + return "\n" + renderTemplate(template, { current_minutes: currentMinutes, suggested_minutes: suggestedMinutes }); } /** diff --git a/actions/setup/md/agent_timeout.md b/actions/setup/md/agent_timeout.md new file mode 100644 index 0000000000..c8f1860bf0 --- /dev/null +++ b/actions/setup/md/agent_timeout.md @@ -0,0 +1,9 @@ +**⏱️ Agent Timed Out**: The agent job exceeded the maximum allowed execution time ({current_minutes} minutes). + +To increase the timeout, add or update the `timeout-minutes` setting in your workflow's frontmatter: + +```yaml +--- +timeout-minutes: {suggested_minutes} +--- +``` From ad31df1d923b14eb7166c30fd3c615a94103bc39 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Mar 2026 05:05:21 +0000 Subject: [PATCH 2/2] test: export buildTimeoutContext and add unit tests Export buildTimeoutContext from handle_agent_failure.cjs so it can be tested directly, following the same pattern as other exported context builders. Add unit tests that stub fs.readFileSync for agent_timeout.md and assert correct rendering of {current_minutes} and {suggested_minutes} placeholders, including the default-minutes fallback and the +10 suggestion logic. Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> Agent-Logs-Url: https://github.com/github/gh-aw/sessions/991ce53a-a7f5-4337-845b-de6f685d5d0f --- actions/setup/js/handle_agent_failure.cjs | 2 +- .../setup/js/handle_agent_failure.test.cjs | 53 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/actions/setup/js/handle_agent_failure.cjs b/actions/setup/js/handle_agent_failure.cjs index 30116d4f2b..50ad555e41 100644 --- a/actions/setup/js/handle_agent_failure.cjs +++ b/actions/setup/js/handle_agent_failure.cjs @@ -1162,4 +1162,4 @@ async function main() { } } -module.exports = { main, buildCodePushFailureContext, buildPushRepoMemoryFailureContext, buildAppTokenMintingFailedContext, buildLockdownCheckFailedContext }; +module.exports = { main, buildCodePushFailureContext, buildPushRepoMemoryFailureContext, buildAppTokenMintingFailedContext, buildLockdownCheckFailedContext, buildTimeoutContext }; diff --git a/actions/setup/js/handle_agent_failure.test.cjs b/actions/setup/js/handle_agent_failure.test.cjs index e6a253bad8..86d56fdf21 100644 --- a/actions/setup/js/handle_agent_failure.test.cjs +++ b/actions/setup/js/handle_agent_failure.test.cjs @@ -389,4 +389,57 @@ describe("handle_agent_failure", () => { expect(result).toContain("gh aw compile --strict"); }); }); + + // ────────────────────────────────────────────────────── + // buildTimeoutContext + // ────────────────────────────────────────────────────── + + describe("buildTimeoutContext", () => { + let buildTimeoutContext; + const fs = require("fs"); + const path = require("path"); + const templateContent = fs.readFileSync(path.join(__dirname, "../md/agent_timeout.md"), "utf8"); + const originalReadFileSync = fs.readFileSync.bind(fs); + + beforeEach(() => { + vi.resetModules(); + // Stub readFileSync so the runtime path resolves to the source-tree template + fs.readFileSync = (filePath, encoding) => { + if (typeof filePath === "string" && filePath.includes("agent_timeout.md")) { + return templateContent; + } + return originalReadFileSync(filePath, encoding); + }; + ({ buildTimeoutContext } = require("./handle_agent_failure.cjs")); + }); + + afterEach(() => { + fs.readFileSync = originalReadFileSync; + }); + + it("returns empty string when not timed out", () => { + expect(buildTimeoutContext(false, "20")).toBe(""); + expect(buildTimeoutContext(false, "")).toBe(""); + }); + + it("returns formatted error message when timed out", () => { + const result = buildTimeoutContext(true, "20"); + expect(result).toContain("Agent Timed Out"); + expect(result).toContain("20"); + expect(result).toContain("30"); + expect(result).toContain("timeout-minutes"); + }); + + it("uses default of 20 minutes when timeoutMinutes is empty", () => { + const result = buildTimeoutContext(true, ""); + expect(result).toContain("20"); + expect(result).toContain("30"); + }); + + it("suggests current + 10 minutes", () => { + const result = buildTimeoutContext(true, "45"); + expect(result).toContain("45"); + expect(result).toContain("55"); + }); + }); });