From 8aaa0a04ec77af2c676df060dcb2f491ca836625 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 03:44:58 +0000 Subject: [PATCH 1/4] Initial plan From df9662c3a24f45c0d81e561e3aeab1c3e536f5bd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 04:09:08 +0000 Subject: [PATCH 2/4] fix: handle auth failures in copilot-driver --resume attempts - Add NO_AUTH_INFO_PATTERN to detect 'No authentication information found' - Auth errors are now non-retryable (like MCP policy errors), preventing 3 useless retries when COPILOT_GITHUB_TOKEN is absent/invalid - Add buildSpawnEnv() that falls back to GITHUB_TOKEN then GH_TOKEN when COPILOT_GITHUB_TOKEN is absent or empty, fixing auth in AWF mode where COPILOT_GITHUB_TOKEN is excluded via --exclude-env - Log auth token availability (names only, never values) for diagnostics - Add tests for auth error detection, retry prevention, and fallback logic" Agent-Logs-Url: https://github.com/github/gh-aw/sessions/9dead9c3-7717-465c-bbd8-027abfb51b7d Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .changeset/patch-fix-resume-auth-failure.md | 5 + actions/setup/js/copilot_driver.cjs | 83 +++++++++++- actions/setup/js/copilot_driver.test.cjs | 134 ++++++++++++++++++++ 3 files changed, 219 insertions(+), 3 deletions(-) create mode 100644 .changeset/patch-fix-resume-auth-failure.md diff --git a/.changeset/patch-fix-resume-auth-failure.md b/.changeset/patch-fix-resume-auth-failure.md new file mode 100644 index 00000000000..1bd3ed3c5f0 --- /dev/null +++ b/.changeset/patch-fix-resume-auth-failure.md @@ -0,0 +1,5 @@ +--- +"gh-aw": patch +--- + +Fix copilot-driver `--resume` authentication failures: detect "No authentication information found" as non-retryable, add GITHUB_TOKEN/GH_TOKEN fallback for COPILOT_GITHUB_TOKEN, and log auth token availability for diagnostics. diff --git a/actions/setup/js/copilot_driver.cjs b/actions/setup/js/copilot_driver.cjs index 5a25ede73e2..574a47347de 100644 --- a/actions/setup/js/copilot_driver.cjs +++ b/actions/setup/js/copilot_driver.cjs @@ -15,6 +15,13 @@ * any partial-execution failure is retried — not just CAPIError 400. * - If the process produced no output (failed to start / auth error before any work), the * driver does not retry because there is nothing to resume. + * - "No authentication information found" errors are non-retryable: the absent token will + * remain absent on every subsequent attempt. The driver also applies an auth-token + * fallback before each spawn: if COPILOT_GITHUB_TOKEN is absent or empty, GITHUB_TOKEN + * (the standard GitHub Actions token, always forwarded into the container) is tried next, + * then GH_TOKEN. This prevents auth failures when COPILOT_GITHUB_TOKEN has been + * excluded from the container environment (e.g. by AWF's --exclude-env) while the + * standard GITHUB_TOKEN is still available. * - Retries use exponential backoff: 5s → 10s → 20s (capped at 60s). * - Maximum 3 retry attempts after the initial run. * @@ -43,6 +50,11 @@ const CAPI_ERROR_400_PATTERN = /CAPIError:\s*400/; // This is a persistent policy configuration error — retrying will not help. const MCP_POLICY_BLOCKED_PATTERN = /MCP servers were blocked by policy:/; +// Pattern to detect missing authentication credentials. +// This error means no auth token is available in the environment; retrying will not help +// because the missing token will still be absent on every subsequent attempt. +const NO_AUTH_INFO_PATTERN = /No authentication information found/; + /** * Emit a timestamped diagnostic log line to stderr. * All driver messages are prefixed with "[copilot-driver]" so they are easy to @@ -73,6 +85,54 @@ function isMCPPolicyError(output) { return MCP_POLICY_BLOCKED_PATTERN.test(output); } +/** + * Determines if the collected output contains a "No authentication information found" error. + * This means no auth token (COPILOT_GITHUB_TOKEN, GH_TOKEN, or GITHUB_TOKEN) is available + * in the environment. Retrying will not help because the absent token will remain absent. + * @param {string} output - Collected stdout+stderr from the process + * @returns {boolean} + */ +function isNoAuthInfoError(output) { + return NO_AUTH_INFO_PATTERN.test(output); +} + +/** + * Build the environment object for spawning the copilot subprocess. + * + * Starts from the current process environment and applies auth token fallback: + * if COPILOT_GITHUB_TOKEN is absent or empty, we substitute GITHUB_TOKEN or GH_TOKEN + * (whichever is set first). This ensures the copilot CLI can authenticate on --resume + * attempts even when COPILOT_GITHUB_TOKEN was excluded from the container environment + * (e.g. by AWF's --exclude-env to prevent the agent from reading the raw token) but the + * standard GITHUB_TOKEN Actions token is still forwarded via --env-all. + * + * Auth token availability is always logged (names only, never values) so that failures + * can be diagnosed without exposing secrets in the log. + * + * @returns {NodeJS.ProcessEnv} + */ +function buildSpawnEnv() { + const env = { ...process.env }; + + const hasTokenValue = /** @param {string} name */ name => typeof env[name] === "string" && env[name].length > 0; + + if (!hasTokenValue("COPILOT_GITHUB_TOKEN")) { + if (hasTokenValue("GITHUB_TOKEN")) { + env["COPILOT_GITHUB_TOKEN"] = env["GITHUB_TOKEN"]; + log("auth: COPILOT_GITHUB_TOKEN is absent — using GITHUB_TOKEN as fallback"); + } else if (hasTokenValue("GH_TOKEN")) { + env["COPILOT_GITHUB_TOKEN"] = env["GH_TOKEN"]; + log("auth: COPILOT_GITHUB_TOKEN is absent — using GH_TOKEN as fallback"); + } else { + log("auth: warning — COPILOT_GITHUB_TOKEN, GITHUB_TOKEN, and GH_TOKEN are all absent or empty; the copilot CLI may fail to authenticate"); + } + } else { + log("auth: COPILOT_GITHUB_TOKEN is set"); + } + + return env; +} + /** * Sleep for a specified duration * @param {number} ms - Duration in milliseconds @@ -138,7 +198,7 @@ function runProcess(command, args, attempt) { const child = spawn(command, args, { stdio: ["inherit", "pipe", "pipe"], - env: process.env, + env: buildSpawnEnv(), }); log(`attempt ${attempt + 1}: process started (pid=${child.pid ?? "unknown"})`); @@ -244,10 +304,19 @@ async function main() { // Retry whenever the session was partially executed (hasOutput), using --resume so that // the Copilot CLI can continue from where it left off. CAPIError 400 is the well-known // transient case, but any partial-execution failure is eligible for a resume retry. - // Exception: MCP policy errors are persistent configuration issues — never retry. + // Exceptions: MCP policy errors and auth errors are persistent — never retry. const isCAPIError = isTransientCAPIError(result.output); const isMCPPolicy = isMCPPolicyError(result.output); - log(`attempt ${attempt + 1} failed:` + ` exitCode=${result.exitCode}` + ` isCAPIError400=${isCAPIError}` + ` isMCPPolicyError=${isMCPPolicy}` + ` hasOutput=${result.hasOutput}` + ` retriesRemaining=${MAX_RETRIES - attempt}`); + const isAuthErr = isNoAuthInfoError(result.output); + log( + `attempt ${attempt + 1} failed:` + + ` exitCode=${result.exitCode}` + + ` isCAPIError400=${isCAPIError}` + + ` isMCPPolicyError=${isMCPPolicy}` + + ` isAuthError=${isAuthErr}` + + ` hasOutput=${result.hasOutput}` + + ` retriesRemaining=${MAX_RETRIES - attempt}` + ); // MCP policy errors are persistent — retrying will not help. if (isMCPPolicy) { @@ -255,6 +324,14 @@ async function main() { break; } + // Auth errors are persistent for the duration of the job — retrying will not help. + // "No authentication information found" means COPILOT_GITHUB_TOKEN / GH_TOKEN / GITHUB_TOKEN + // are all absent or invalid. Retrying with --resume will produce the same auth failure. + if (isAuthErr) { + log(`attempt ${attempt + 1}: no authentication information found — not retrying (COPILOT_GITHUB_TOKEN, GH_TOKEN, and GITHUB_TOKEN are all absent or invalid)`); + break; + } + if (attempt < MAX_RETRIES && result.hasOutput) { const reason = isCAPIError ? "CAPIError 400 (transient)" : "partial execution"; log(`attempt ${attempt + 1}: ${reason} — will retry with --resume (attempt ${attempt + 2}/${MAX_RETRIES + 1})`); diff --git a/actions/setup/js/copilot_driver.test.cjs b/actions/setup/js/copilot_driver.test.cjs index 37ff8f843c2..008ec334598 100644 --- a/actions/setup/js/copilot_driver.test.cjs +++ b/actions/setup/js/copilot_driver.test.cjs @@ -141,6 +141,140 @@ describe("copilot_driver.cjs", () => { }); }); + describe("no-auth-info detection pattern", () => { + const NO_AUTH_INFO_PATTERN = /No authentication information found/; + + it("matches the exact error from the issue report", () => { + const errorOutput = + "Error: No authentication information found.\n" + + "Copilot can be authenticated with GitHub using an OAuth Token or a Fine-Grained Personal Access Token.\n" + + "To authenticate, you can use any of the following methods:\n" + + " - Start 'copilot' and run the '/login' command\n" + + " - Set the COPILOT_GITHUB_TOKEN, GH_TOKEN, or GITHUB_TOKEN environment variable\n" + + " - Run 'gh auth login' to authenticate with the GitHub CLI"; + expect(NO_AUTH_INFO_PATTERN.test(errorOutput)).toBe(true); + }); + + it("matches when embedded in larger output after a long run", () => { + const output = "Some agent work output\nMore work\nNo authentication information found\nEnd"; + expect(NO_AUTH_INFO_PATTERN.test(output)).toBe(true); + }); + + it("does not match unrelated auth errors", () => { + expect(NO_AUTH_INFO_PATTERN.test("Access denied by policy settings")).toBe(false); + expect(NO_AUTH_INFO_PATTERN.test("Error: 401 Unauthorized")).toBe(false); + expect(NO_AUTH_INFO_PATTERN.test("Authentication failed")).toBe(false); + expect(NO_AUTH_INFO_PATTERN.test("CAPIError: 400 Bad Request")).toBe(false); + expect(NO_AUTH_INFO_PATTERN.test("")).toBe(false); + }); + }); + + describe("auth error prevents retry", () => { + // Inline the same retry logic as the driver, including auth error check + const MCP_POLICY_BLOCKED_PATTERN = /MCP servers were blocked by policy:/; + const NO_AUTH_INFO_PATTERN = /No authentication information found/; + const MAX_RETRIES = 3; + + /** + * @param {{hasOutput: boolean, exitCode: number, output: string}} result + * @param {number} attempt + * @returns {boolean} + */ + function shouldRetry(result, attempt) { + if (result.exitCode === 0) return false; + // MCP policy errors are persistent — never retry + if (MCP_POLICY_BLOCKED_PATTERN.test(result.output)) return false; + // Auth errors are persistent — never retry + if (NO_AUTH_INFO_PATTERN.test(result.output)) return false; + return attempt < MAX_RETRIES && result.hasOutput; + } + + it("does not retry when auth fails on first attempt (no real work done)", () => { + const result = { exitCode: 1, hasOutput: true, output: "Error: No authentication information found." }; + expect(shouldRetry(result, 0)).toBe(false); + }); + + it("does not retry when auth fails on a --resume attempt (the reported bug scenario)", () => { + // This replicates the issue: attempt 1 ran for 39 min then failed, + // attempt 2 (--resume) fails with auth error — should not retry attempts 3 & 4. + const resumeResult = { exitCode: 1, hasOutput: true, output: "Error: No authentication information found." }; + expect(shouldRetry(resumeResult, 1)).toBe(false); + expect(shouldRetry(resumeResult, 2)).toBe(false); + expect(shouldRetry(resumeResult, 3)).toBe(false); + }); + + it("does not retry auth error even when output is mixed with other content", () => { + const result = { exitCode: 1, hasOutput: true, output: "Some output\nError: No authentication information found.\nMore output" }; + expect(shouldRetry(result, 0)).toBe(false); + }); + + it("still retries non-auth errors with output (CAPIError 400)", () => { + const result = { exitCode: 1, hasOutput: true, output: "CAPIError: 400 Bad Request" }; + expect(shouldRetry(result, 0)).toBe(true); + }); + + it("still retries generic partial-execution errors with output", () => { + const result = { exitCode: 1, hasOutput: true, output: "Failed to get response from the AI model; retried 5 times" }; + expect(shouldRetry(result, 0)).toBe(true); + }); + }); + + describe("auth token fallback (buildSpawnEnv logic)", () => { + // Inline the same logic as buildSpawnEnv() for unit testing without side effects. + /** + * @param {Record} env - Simulated environment + * @returns {{ COPILOT_GITHUB_TOKEN?: string, warning?: string }} + */ + function applyAuthFallback(env) { + const result = { ...env }; + const hasTokenValue = /** @param {string} name */ name => typeof result[name] === "string" && result[name].length > 0; + + if (!hasTokenValue("COPILOT_GITHUB_TOKEN")) { + if (hasTokenValue("GITHUB_TOKEN")) { + result["COPILOT_GITHUB_TOKEN"] = result["GITHUB_TOKEN"]; + } else if (hasTokenValue("GH_TOKEN")) { + result["COPILOT_GITHUB_TOKEN"] = result["GH_TOKEN"]; + } + } + return result; + } + + it("passes COPILOT_GITHUB_TOKEN unchanged when already set", () => { + const result = applyAuthFallback({ COPILOT_GITHUB_TOKEN: "ghs_secret" }); + expect(result.COPILOT_GITHUB_TOKEN).toBe("ghs_secret"); + }); + + it("falls back to GITHUB_TOKEN when COPILOT_GITHUB_TOKEN is absent", () => { + const result = applyAuthFallback({ GITHUB_TOKEN: "ghs_actions_token" }); + expect(result.COPILOT_GITHUB_TOKEN).toBe("ghs_actions_token"); + }); + + it("falls back to GH_TOKEN when both COPILOT_GITHUB_TOKEN and GITHUB_TOKEN are absent", () => { + const result = applyAuthFallback({ GH_TOKEN: "ghp_pat_token" }); + expect(result.COPILOT_GITHUB_TOKEN).toBe("ghp_pat_token"); + }); + + it("prefers GITHUB_TOKEN over GH_TOKEN when both are present", () => { + const result = applyAuthFallback({ GITHUB_TOKEN: "ghs_actions", GH_TOKEN: "ghp_pat" }); + expect(result.COPILOT_GITHUB_TOKEN).toBe("ghs_actions"); + }); + + it("leaves COPILOT_GITHUB_TOKEN unset when all auth tokens are absent", () => { + const result = applyAuthFallback({}); + expect(result.COPILOT_GITHUB_TOKEN).toBeUndefined(); + }); + + it("treats empty-string COPILOT_GITHUB_TOKEN as absent and applies fallback", () => { + const result = applyAuthFallback({ COPILOT_GITHUB_TOKEN: "", GITHUB_TOKEN: "ghs_actions" }); + expect(result.COPILOT_GITHUB_TOKEN).toBe("ghs_actions"); + }); + + it("does not overwrite a valid COPILOT_GITHUB_TOKEN with a fallback", () => { + const result = applyAuthFallback({ COPILOT_GITHUB_TOKEN: "ghs_original", GITHUB_TOKEN: "ghs_actions" }); + expect(result.COPILOT_GITHUB_TOKEN).toBe("ghs_original"); + }); + }); + describe("retry configuration", () => { it("has sensible default values", () => { // These match the constants in copilot_driver.cjs From bd9d744c3ca84653a1fc7eb76ea616421e49ff13 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 04:23:38 +0000 Subject: [PATCH 3/4] fix: remove auth token fallback from copilot_driver (won't help) Remove buildSpawnEnv() and GITHUB_TOKEN/GH_TOKEN fallback logic per review feedback. The fallback has no effect in AWF mode (all tokens absent in container) and in non-AWF mode (COPILOT_GITHUB_TOKEN is always present). Keep the non-retryable auth error detection which does prevent useless --resume retries on persistent auth failures. Agent-Logs-Url: https://github.com/github/gh-aw/sessions/cc5b1c53-6d0c-4862-81ff-f4d6d455018a Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/copilot_driver.cjs | 46 +------------------ actions/setup/js/copilot_driver.test.cjs | 56 ------------------------ 2 files changed, 2 insertions(+), 100 deletions(-) diff --git a/actions/setup/js/copilot_driver.cjs b/actions/setup/js/copilot_driver.cjs index 574a47347de..d5e389639de 100644 --- a/actions/setup/js/copilot_driver.cjs +++ b/actions/setup/js/copilot_driver.cjs @@ -16,12 +16,7 @@ * - If the process produced no output (failed to start / auth error before any work), the * driver does not retry because there is nothing to resume. * - "No authentication information found" errors are non-retryable: the absent token will - * remain absent on every subsequent attempt. The driver also applies an auth-token - * fallback before each spawn: if COPILOT_GITHUB_TOKEN is absent or empty, GITHUB_TOKEN - * (the standard GitHub Actions token, always forwarded into the container) is tried next, - * then GH_TOKEN. This prevents auth failures when COPILOT_GITHUB_TOKEN has been - * excluded from the container environment (e.g. by AWF's --exclude-env) while the - * standard GITHUB_TOKEN is still available. + * remain absent on every subsequent attempt, so all further retries will also fail. * - Retries use exponential backoff: 5s → 10s → 20s (capped at 60s). * - Maximum 3 retry attempts after the initial run. * @@ -96,43 +91,6 @@ function isNoAuthInfoError(output) { return NO_AUTH_INFO_PATTERN.test(output); } -/** - * Build the environment object for spawning the copilot subprocess. - * - * Starts from the current process environment and applies auth token fallback: - * if COPILOT_GITHUB_TOKEN is absent or empty, we substitute GITHUB_TOKEN or GH_TOKEN - * (whichever is set first). This ensures the copilot CLI can authenticate on --resume - * attempts even when COPILOT_GITHUB_TOKEN was excluded from the container environment - * (e.g. by AWF's --exclude-env to prevent the agent from reading the raw token) but the - * standard GITHUB_TOKEN Actions token is still forwarded via --env-all. - * - * Auth token availability is always logged (names only, never values) so that failures - * can be diagnosed without exposing secrets in the log. - * - * @returns {NodeJS.ProcessEnv} - */ -function buildSpawnEnv() { - const env = { ...process.env }; - - const hasTokenValue = /** @param {string} name */ name => typeof env[name] === "string" && env[name].length > 0; - - if (!hasTokenValue("COPILOT_GITHUB_TOKEN")) { - if (hasTokenValue("GITHUB_TOKEN")) { - env["COPILOT_GITHUB_TOKEN"] = env["GITHUB_TOKEN"]; - log("auth: COPILOT_GITHUB_TOKEN is absent — using GITHUB_TOKEN as fallback"); - } else if (hasTokenValue("GH_TOKEN")) { - env["COPILOT_GITHUB_TOKEN"] = env["GH_TOKEN"]; - log("auth: COPILOT_GITHUB_TOKEN is absent — using GH_TOKEN as fallback"); - } else { - log("auth: warning — COPILOT_GITHUB_TOKEN, GITHUB_TOKEN, and GH_TOKEN are all absent or empty; the copilot CLI may fail to authenticate"); - } - } else { - log("auth: COPILOT_GITHUB_TOKEN is set"); - } - - return env; -} - /** * Sleep for a specified duration * @param {number} ms - Duration in milliseconds @@ -198,7 +156,7 @@ function runProcess(command, args, attempt) { const child = spawn(command, args, { stdio: ["inherit", "pipe", "pipe"], - env: buildSpawnEnv(), + env: process.env, }); log(`attempt ${attempt + 1}: process started (pid=${child.pid ?? "unknown"})`); diff --git a/actions/setup/js/copilot_driver.test.cjs b/actions/setup/js/copilot_driver.test.cjs index 008ec334598..3ff14f9f2ac 100644 --- a/actions/setup/js/copilot_driver.test.cjs +++ b/actions/setup/js/copilot_driver.test.cjs @@ -219,62 +219,6 @@ describe("copilot_driver.cjs", () => { }); }); - describe("auth token fallback (buildSpawnEnv logic)", () => { - // Inline the same logic as buildSpawnEnv() for unit testing without side effects. - /** - * @param {Record} env - Simulated environment - * @returns {{ COPILOT_GITHUB_TOKEN?: string, warning?: string }} - */ - function applyAuthFallback(env) { - const result = { ...env }; - const hasTokenValue = /** @param {string} name */ name => typeof result[name] === "string" && result[name].length > 0; - - if (!hasTokenValue("COPILOT_GITHUB_TOKEN")) { - if (hasTokenValue("GITHUB_TOKEN")) { - result["COPILOT_GITHUB_TOKEN"] = result["GITHUB_TOKEN"]; - } else if (hasTokenValue("GH_TOKEN")) { - result["COPILOT_GITHUB_TOKEN"] = result["GH_TOKEN"]; - } - } - return result; - } - - it("passes COPILOT_GITHUB_TOKEN unchanged when already set", () => { - const result = applyAuthFallback({ COPILOT_GITHUB_TOKEN: "ghs_secret" }); - expect(result.COPILOT_GITHUB_TOKEN).toBe("ghs_secret"); - }); - - it("falls back to GITHUB_TOKEN when COPILOT_GITHUB_TOKEN is absent", () => { - const result = applyAuthFallback({ GITHUB_TOKEN: "ghs_actions_token" }); - expect(result.COPILOT_GITHUB_TOKEN).toBe("ghs_actions_token"); - }); - - it("falls back to GH_TOKEN when both COPILOT_GITHUB_TOKEN and GITHUB_TOKEN are absent", () => { - const result = applyAuthFallback({ GH_TOKEN: "ghp_pat_token" }); - expect(result.COPILOT_GITHUB_TOKEN).toBe("ghp_pat_token"); - }); - - it("prefers GITHUB_TOKEN over GH_TOKEN when both are present", () => { - const result = applyAuthFallback({ GITHUB_TOKEN: "ghs_actions", GH_TOKEN: "ghp_pat" }); - expect(result.COPILOT_GITHUB_TOKEN).toBe("ghs_actions"); - }); - - it("leaves COPILOT_GITHUB_TOKEN unset when all auth tokens are absent", () => { - const result = applyAuthFallback({}); - expect(result.COPILOT_GITHUB_TOKEN).toBeUndefined(); - }); - - it("treats empty-string COPILOT_GITHUB_TOKEN as absent and applies fallback", () => { - const result = applyAuthFallback({ COPILOT_GITHUB_TOKEN: "", GITHUB_TOKEN: "ghs_actions" }); - expect(result.COPILOT_GITHUB_TOKEN).toBe("ghs_actions"); - }); - - it("does not overwrite a valid COPILOT_GITHUB_TOKEN with a fallback", () => { - const result = applyAuthFallback({ COPILOT_GITHUB_TOKEN: "ghs_original", GITHUB_TOKEN: "ghs_actions" }); - expect(result.COPILOT_GITHUB_TOKEN).toBe("ghs_original"); - }); - }); - describe("retry configuration", () => { it("has sensible default values", () => { // These match the constants in copilot_driver.cjs From 541fcc2c26c0ddf93b9804daf51005f384273a78 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 14:23:40 +0000 Subject: [PATCH 4/4] fix(copilot-driver): use --continue instead of --resume for retry attempts Agent-Logs-Url: https://github.com/github/gh-aw/sessions/7f9600ad-2cc9-463c-b8a3-0f16a7defff0 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/copilot_driver.cjs | 16 ++++++++-------- actions/setup/js/copilot_driver.test.cjs | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/actions/setup/js/copilot_driver.cjs b/actions/setup/js/copilot_driver.cjs index d5e389639de..7ce238f9284 100644 --- a/actions/setup/js/copilot_driver.cjs +++ b/actions/setup/js/copilot_driver.cjs @@ -9,7 +9,7 @@ * * Retry policy: * - If the process produced any output (hasOutput) and exits with a non-zero code, the - * session is considered partially executed. The driver retries with --resume so the + * session is considered partially executed. The driver retries with --continue so the * Copilot CLI can continue from where it left off. * - CAPIError 400 is a well-known transient failure mode and is logged explicitly, but * any partial-execution failure is retried — not just CAPIError 400. @@ -239,11 +239,11 @@ async function main() { const driverStartTime = Date.now(); for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) { - // Add --resume flag on retries so the copilot session resumes from where it left off - const currentArgs = attempt > 0 ? [...args, "--resume"] : args; + // Add --continue flag on retries so the copilot session continues from where it left off + const currentArgs = attempt > 0 ? [...args, "--continue"] : args; if (attempt > 0) { - log(`retry ${attempt}/${MAX_RETRIES}: sleeping ${delay}ms before next attempt with --resume`); + log(`retry ${attempt}/${MAX_RETRIES}: sleeping ${delay}ms before next attempt with --continue`); await sleep(delay); delay = Math.min(delay * BACKOFF_MULTIPLIER, MAX_DELAY_MS); log(`retry ${attempt}/${MAX_RETRIES}: woke up, next delay cap will be ${Math.min(delay * BACKOFF_MULTIPLIER, MAX_DELAY_MS)}ms`); @@ -259,9 +259,9 @@ async function main() { } // Determine whether to retry. - // Retry whenever the session was partially executed (hasOutput), using --resume so that + // Retry whenever the session was partially executed (hasOutput), using --continue so that // the Copilot CLI can continue from where it left off. CAPIError 400 is the well-known - // transient case, but any partial-execution failure is eligible for a resume retry. + // transient case, but any partial-execution failure is eligible for a continue retry. // Exceptions: MCP policy errors and auth errors are persistent — never retry. const isCAPIError = isTransientCAPIError(result.output); const isMCPPolicy = isMCPPolicyError(result.output); @@ -284,7 +284,7 @@ async function main() { // Auth errors are persistent for the duration of the job — retrying will not help. // "No authentication information found" means COPILOT_GITHUB_TOKEN / GH_TOKEN / GITHUB_TOKEN - // are all absent or invalid. Retrying with --resume will produce the same auth failure. + // are all absent or invalid. Retrying with --continue will produce the same auth failure. if (isAuthErr) { log(`attempt ${attempt + 1}: no authentication information found — not retrying (COPILOT_GITHUB_TOKEN, GH_TOKEN, and GITHUB_TOKEN are all absent or invalid)`); break; @@ -292,7 +292,7 @@ async function main() { if (attempt < MAX_RETRIES && result.hasOutput) { const reason = isCAPIError ? "CAPIError 400 (transient)" : "partial execution"; - log(`attempt ${attempt + 1}: ${reason} — will retry with --resume (attempt ${attempt + 2}/${MAX_RETRIES + 1})`); + log(`attempt ${attempt + 1}: ${reason} — will retry with --continue (attempt ${attempt + 2}/${MAX_RETRIES + 1})`); continue; } diff --git a/actions/setup/js/copilot_driver.test.cjs b/actions/setup/js/copilot_driver.test.cjs index 3ff14f9f2ac..dc8f5799dfc 100644 --- a/actions/setup/js/copilot_driver.test.cjs +++ b/actions/setup/js/copilot_driver.test.cjs @@ -34,7 +34,7 @@ describe("copilot_driver.cjs", () => { }); }); - describe("retry policy: resume on partial execution", () => { + describe("retry policy: continue on partial execution", () => { // Inline the same retry-eligibility logic as the driver for unit testing. // The driver retries whenever the session produced output (hasOutput), regardless // of the specific error type. CAPIError 400 is just the well-known case. @@ -194,9 +194,9 @@ describe("copilot_driver.cjs", () => { expect(shouldRetry(result, 0)).toBe(false); }); - it("does not retry when auth fails on a --resume attempt (the reported bug scenario)", () => { + it("does not retry when auth fails on a --continue attempt (the reported bug scenario)", () => { // This replicates the issue: attempt 1 ran for 39 min then failed, - // attempt 2 (--resume) fails with auth error — should not retry attempts 3 & 4. + // attempt 2 (--continue) fails with auth error — should not retry attempts 3 & 4. const resumeResult = { exitCode: 1, hasOutput: true, output: "Error: No authentication information found." }; expect(shouldRetry(resumeResult, 1)).toBe(false); expect(shouldRetry(resumeResult, 2)).toBe(false);