From 4dc7ac39fd3e5a8fa763e67783ac559cc026cdbe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 13:07:54 +0000 Subject: [PATCH 01/13] Initial plan From ec60fc96366d2c337cbcc02b050f53c99a6de890 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 13:27:27 +0000 Subject: [PATCH 02/13] Handle missing Copilot proxy auth template gracefully Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/copilot_harness.cjs | 24 ++++++++++++++++++----- actions/setup/js/copilot_harness.test.cjs | 24 +++++++++++++++++++++++ 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/actions/setup/js/copilot_harness.cjs b/actions/setup/js/copilot_harness.cjs index 9d02b4ac921..33a5d4540fe 100644 --- a/actions/setup/js/copilot_harness.cjs +++ b/actions/setup/js/copilot_harness.cjs @@ -82,6 +82,11 @@ const MAX_ENV_VAR_PREVIEW_LENGTH = 120; const OUTPUT_TAIL_MAX_CHARS = 600; const OUTPUT_TAIL_MAX_LINES = 12; const COPILOT_REQUESTS_PROXY_AUTH_403_TEMPLATE_PATH = path.join(__dirname, "../md/copilot_requests_proxy_auth_403.md"); +const COPILOT_REQUESTS_PROXY_AUTH_403_FALLBACK_TEMPLATE = + "Copilot requests authentication failed through the gh-aw API proxy (HTTP 403, model={selected_model}, stage={stage}). " + + "This workflow is using permissions.copilot-requests: write, so Copilot requests must be allowed through your organization's centralized Copilot billing configuration. " + + "Verify that copilot-requests: write is granted to the workflow or job and that Copilot org billing is enabled for your organization. " + + "See https://github.github.com/gh-aw/reference/billing/ for details."; // Pattern to detect transient CAPIError 400 in copilot output const CAPI_ERROR_400_PATTERN = /CAPIError:\s*400/; @@ -420,9 +425,10 @@ function envFlagEnabled(value) { * Build a more actionable Copilot auth diagnostic when a 401/403 came from the gh-aw API proxy. * @param {string} output * @param {NodeJS.ProcessEnv} [env] + * @param {{ renderTemplateFromFile?: typeof renderTemplateFromFile, logger?: typeof log }} [options] * @returns {string} */ -function buildCopilotProxyAuthFailureDiagnostic(output, env = process.env) { +function buildCopilotProxyAuthFailureDiagnostic(output, env = process.env, options = {}) { const authFailure = parseProviderAuthFailure(output); if (!authFailure || !isLikelyAWFAPIProxyURL(authFailure.providerUrl)) { return ""; @@ -431,10 +437,18 @@ function buildCopilotProxyAuthFailureDiagnostic(output, env = process.env) { const selectedModel = typeof env.COPILOT_MODEL === "string" && env.COPILOT_MODEL.trim() ? env.COPILOT_MODEL.trim() : "(unset)"; const stage = detectCopilotAuthFailureStage(output); if (authFailure.statusCode === "403" && envFlagEnabled(env.S2STOKENS)) { - return renderTemplateFromFile(COPILOT_REQUESTS_PROXY_AUTH_403_TEMPLATE_PATH, { - selected_model: selectedModel, - stage, - }); + const render = options.renderTemplateFromFile || renderTemplateFromFile; + const logger = options.logger || log; + try { + return render(COPILOT_REQUESTS_PROXY_AUTH_403_TEMPLATE_PATH, { + selected_model: selectedModel, + stage, + }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger(`proxy auth diagnostic template unavailable (${COPILOT_REQUESTS_PROXY_AUTH_403_TEMPLATE_PATH}): ${message}`); + return COPILOT_REQUESTS_PROXY_AUTH_403_FALLBACK_TEMPLATE.replace("{selected_model}", selectedModel).replace("{stage}", stage); + } } if (authFailure.statusCode !== "401") { return ""; diff --git a/actions/setup/js/copilot_harness.test.cjs b/actions/setup/js/copilot_harness.test.cjs index b413140382e..18472f6cbea 100644 --- a/actions/setup/js/copilot_harness.test.cjs +++ b/actions/setup/js/copilot_harness.test.cjs @@ -1000,6 +1000,30 @@ describe("copilot_harness.cjs", () => { expect(diagnostic).not.toContain("COPILOT_PROVIDER_API_KEY"); }); + it("falls back to inline 403 guidance when the template file is unavailable", () => { + const logger = vi.fn(); + const diagnostic = buildCopilotProxyAuthFailureDiagnostic( + "Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).", + { + COPILOT_MODEL: "claude-sonnet-4.5", + S2STOKENS: "true", + }, + { + renderTemplateFromFile: () => { + throw new Error("ENOENT: missing template"); + }, + logger, + } + ); + + expect(diagnostic).toContain("Copilot requests authentication failed"); + expect(diagnostic).toContain("HTTP 403"); + expect(diagnostic).toContain("model=claude-sonnet-4.5"); + expect(diagnostic).toContain("stage=starting the Copilot CLI request"); + expect(diagnostic).toContain("centralized Copilot billing"); + expect(logger).toHaveBeenCalledWith(expect.stringContaining("proxy auth diagnostic template unavailable")); + }); + it("returns empty string for proxy 403 when S2STOKENS is not set (BYOK mode)", () => { const diagnostic = buildCopilotProxyAuthFailureDiagnostic("Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).", { COPILOT_MODEL: "claude-sonnet-4.5", From 0eb4a79f9c8756493975db6aa4a52f43985244cb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 13:36:33 +0000 Subject: [PATCH 03/13] Polish Copilot auth fallback formatting Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/copilot_harness.cjs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/actions/setup/js/copilot_harness.cjs b/actions/setup/js/copilot_harness.cjs index 33a5d4540fe..c8a6826330e 100644 --- a/actions/setup/js/copilot_harness.cjs +++ b/actions/setup/js/copilot_harness.cjs @@ -44,7 +44,7 @@ require("./shim.cjs"); const fs = require("fs"); const path = require("path"); const crypto = require("crypto"); -const { renderTemplateFromFile } = require("./messages_core.cjs"); +const { renderTemplate, renderTemplateFromFile } = require("./messages_core.cjs"); const { runProcess, formatDuration, sleep, isCopilotSDKEnabled, buildCopilotSDKEnv } = require("./process_runner.cjs"); const { buildCopilotSDKServerArgs, getCopilotSDKServerPort, startCopilotSDKServer, stopCopilotSDKServer, waitForCopilotSDKServer } = require("./copilot_sdk_sidecar.cjs"); const { @@ -81,12 +81,14 @@ const PROMPT_FILE_INLINE_THRESHOLD_LABEL = "100KB"; const MAX_ENV_VAR_PREVIEW_LENGTH = 120; const OUTPUT_TAIL_MAX_CHARS = 600; const OUTPUT_TAIL_MAX_LINES = 12; +const GH_AW_BILLING_REFERENCE_URL = "https://github.github.com/gh-aw/reference/billing/"; const COPILOT_REQUESTS_PROXY_AUTH_403_TEMPLATE_PATH = path.join(__dirname, "../md/copilot_requests_proxy_auth_403.md"); -const COPILOT_REQUESTS_PROXY_AUTH_403_FALLBACK_TEMPLATE = - "Copilot requests authentication failed through the gh-aw API proxy (HTTP 403, model={selected_model}, stage={stage}). " + - "This workflow is using permissions.copilot-requests: write, so Copilot requests must be allowed through your organization's centralized Copilot billing configuration. " + - "Verify that copilot-requests: write is granted to the workflow or job and that Copilot org billing is enabled for your organization. " + - "See https://github.github.com/gh-aw/reference/billing/ for details."; +const COPILOT_REQUESTS_PROXY_AUTH_403_FALLBACK_TEMPLATE = [ + "Copilot requests authentication failed through the gh-aw API proxy (HTTP 403, model={selected_model}, stage={stage}). ", + "This workflow is using permissions.copilot-requests: write, so Copilot requests must be allowed through your organization's centralized Copilot billing configuration. ", + "Verify that copilot-requests: write is granted to the workflow or job and that Copilot org billing is enabled for your organization. ", + `See ${GH_AW_BILLING_REFERENCE_URL} for details.`, +].join(""); // Pattern to detect transient CAPIError 400 in copilot output const CAPI_ERROR_400_PATTERN = /CAPIError:\s*400/; @@ -447,7 +449,10 @@ function buildCopilotProxyAuthFailureDiagnostic(output, env = process.env, optio } catch (error) { const message = error instanceof Error ? error.message : String(error); logger(`proxy auth diagnostic template unavailable (${COPILOT_REQUESTS_PROXY_AUTH_403_TEMPLATE_PATH}): ${message}`); - return COPILOT_REQUESTS_PROXY_AUTH_403_FALLBACK_TEMPLATE.replace("{selected_model}", selectedModel).replace("{stage}", stage); + return renderTemplate(COPILOT_REQUESTS_PROXY_AUTH_403_FALLBACK_TEMPLATE, { + selected_model: selectedModel, + stage, + }); } } if (authFailure.statusCode !== "401") { From c737b1c5ed4f3c5ecfa8b689e13ec4a73f32563a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 14:49:03 +0000 Subject: [PATCH 04/13] Resolve Copilot proxy auth template path Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/copilot_harness.cjs | 32 ++----- actions/setup/js/copilot_harness.test.cjs | 104 ++++++++++++++-------- 2 files changed, 72 insertions(+), 64 deletions(-) diff --git a/actions/setup/js/copilot_harness.cjs b/actions/setup/js/copilot_harness.cjs index c8a6826330e..7b58d677ffc 100644 --- a/actions/setup/js/copilot_harness.cjs +++ b/actions/setup/js/copilot_harness.cjs @@ -42,9 +42,8 @@ require("./shim.cjs"); const fs = require("fs"); -const path = require("path"); const crypto = require("crypto"); -const { renderTemplate, renderTemplateFromFile } = require("./messages_core.cjs"); +const { getPromptPath, renderTemplateFromFile } = require("./messages_core.cjs"); const { runProcess, formatDuration, sleep, isCopilotSDKEnabled, buildCopilotSDKEnv } = require("./process_runner.cjs"); const { buildCopilotSDKServerArgs, getCopilotSDKServerPort, startCopilotSDKServer, stopCopilotSDKServer, waitForCopilotSDKServer } = require("./copilot_sdk_sidecar.cjs"); const { @@ -81,14 +80,7 @@ const PROMPT_FILE_INLINE_THRESHOLD_LABEL = "100KB"; const MAX_ENV_VAR_PREVIEW_LENGTH = 120; const OUTPUT_TAIL_MAX_CHARS = 600; const OUTPUT_TAIL_MAX_LINES = 12; -const GH_AW_BILLING_REFERENCE_URL = "https://github.github.com/gh-aw/reference/billing/"; -const COPILOT_REQUESTS_PROXY_AUTH_403_TEMPLATE_PATH = path.join(__dirname, "../md/copilot_requests_proxy_auth_403.md"); -const COPILOT_REQUESTS_PROXY_AUTH_403_FALLBACK_TEMPLATE = [ - "Copilot requests authentication failed through the gh-aw API proxy (HTTP 403, model={selected_model}, stage={stage}). ", - "This workflow is using permissions.copilot-requests: write, so Copilot requests must be allowed through your organization's centralized Copilot billing configuration. ", - "Verify that copilot-requests: write is granted to the workflow or job and that Copilot org billing is enabled for your organization. ", - `See ${GH_AW_BILLING_REFERENCE_URL} for details.`, -].join(""); +const COPILOT_REQUESTS_PROXY_AUTH_403_TEMPLATE_NAME = "copilot_requests_proxy_auth_403.md"; // Pattern to detect transient CAPIError 400 in copilot output const CAPI_ERROR_400_PATTERN = /CAPIError:\s*400/; @@ -427,7 +419,7 @@ function envFlagEnabled(value) { * Build a more actionable Copilot auth diagnostic when a 401/403 came from the gh-aw API proxy. * @param {string} output * @param {NodeJS.ProcessEnv} [env] - * @param {{ renderTemplateFromFile?: typeof renderTemplateFromFile, logger?: typeof log }} [options] + * @param {{ renderTemplateFromFile?: typeof renderTemplateFromFile }} [options] * @returns {string} */ function buildCopilotProxyAuthFailureDiagnostic(output, env = process.env, options = {}) { @@ -440,20 +432,10 @@ function buildCopilotProxyAuthFailureDiagnostic(output, env = process.env, optio const stage = detectCopilotAuthFailureStage(output); if (authFailure.statusCode === "403" && envFlagEnabled(env.S2STOKENS)) { const render = options.renderTemplateFromFile || renderTemplateFromFile; - const logger = options.logger || log; - try { - return render(COPILOT_REQUESTS_PROXY_AUTH_403_TEMPLATE_PATH, { - selected_model: selectedModel, - stage, - }); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - logger(`proxy auth diagnostic template unavailable (${COPILOT_REQUESTS_PROXY_AUTH_403_TEMPLATE_PATH}): ${message}`); - return renderTemplate(COPILOT_REQUESTS_PROXY_AUTH_403_FALLBACK_TEMPLATE, { - selected_model: selectedModel, - stage, - }); - } + return render(getPromptPath(COPILOT_REQUESTS_PROXY_AUTH_403_TEMPLATE_NAME), { + selected_model: selectedModel, + stage, + }); } if (authFailure.statusCode !== "401") { return ""; diff --git a/actions/setup/js/copilot_harness.test.cjs b/actions/setup/js/copilot_harness.test.cjs index 18472f6cbea..81934ab6989 100644 --- a/actions/setup/js/copilot_harness.test.cjs +++ b/actions/setup/js/copilot_harness.test.cjs @@ -8,6 +8,7 @@ import path from "path"; const require = createRequire(import.meta.url); const { EventEmitter } = require("events"); const { PassThrough } = require("stream"); +const promptsSourceDir = new URL("../md/", import.meta.url).pathname; const { buildCopilotSDKServerArgs, getCopilotSDKServerPort, startCopilotSDKServer, stopCopilotSDKServer, waitForCopilotSDKServer } = require("./copilot_sdk_sidecar.cjs"); const { buildCopilotSDKEnv, isCopilotSDKEnabled } = require("./process_runner.cjs"); const { @@ -974,54 +975,79 @@ describe("copilot_harness.cjs", () => { }); it("rewrites local proxy 403 errors in copilot-requests mode to org-billing guidance", () => { - const diagnostic = buildCopilotProxyAuthFailureDiagnostic("Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).\nCheck your COPILOT_PROVIDER_API_KEY or COPILOT_PROVIDER_BEARER_TOKEN.", { - COPILOT_MODEL: "claude-sonnet-4.5", - S2STOKENS: "true", - }); + const originalPromptsDir = process.env.GH_AW_PROMPTS_DIR; + process.env.GH_AW_PROMPTS_DIR = promptsSourceDir; + try { + const diagnostic = buildCopilotProxyAuthFailureDiagnostic("Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).\nCheck your COPILOT_PROVIDER_API_KEY or COPILOT_PROVIDER_BEARER_TOKEN.", { + COPILOT_MODEL: "claude-sonnet-4.5", + S2STOKENS: "true", + }); - expect(diagnostic).toContain("Copilot requests authentication failed"); - expect(diagnostic).toContain("HTTP 403"); - expect(diagnostic).toContain("model=claude-sonnet-4.5"); - expect(diagnostic).toContain("stage=starting the Copilot CLI request"); - expect(diagnostic).toContain("permissions.copilot-requests: write"); - expect(diagnostic).toContain("centralized Copilot billing"); - expect(diagnostic).toContain("https://github.github.com/gh-aw/reference/billing/"); - expect(diagnostic).not.toContain("COPILOT_PROVIDER_API_KEY"); + expect(diagnostic).toContain("Copilot requests authentication failed"); + expect(diagnostic).toContain("HTTP 403"); + expect(diagnostic).toContain("model=claude-sonnet-4.5"); + expect(diagnostic).toContain("stage=starting the Copilot CLI request"); + expect(diagnostic).toContain("permissions.copilot-requests: write"); + expect(diagnostic).toContain("centralized Copilot billing"); + expect(diagnostic).toContain("https://github.github.com/gh-aw/reference/billing/"); + expect(diagnostic).not.toContain("COPILOT_PROVIDER_API_KEY"); + } finally { + if (typeof originalPromptsDir === "string") { + process.env.GH_AW_PROMPTS_DIR = originalPromptsDir; + } else { + delete process.env.GH_AW_PROMPTS_DIR; + } + } }); it("treats truthy S2STOKENS values as copilot-requests mode for 403 guidance", () => { - const diagnostic = buildCopilotProxyAuthFailureDiagnostic("Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).", { - COPILOT_MODEL: "claude-sonnet-4.5", - S2STOKENS: " YES ", - }); + const originalPromptsDir = process.env.GH_AW_PROMPTS_DIR; + process.env.GH_AW_PROMPTS_DIR = promptsSourceDir; + try { + const diagnostic = buildCopilotProxyAuthFailureDiagnostic("Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).", { + COPILOT_MODEL: "claude-sonnet-4.5", + S2STOKENS: " YES ", + }); - expect(diagnostic).toContain("Copilot requests authentication failed"); - expect(diagnostic).toContain("https://github.github.com/gh-aw/reference/billing/"); - expect(diagnostic).not.toContain("COPILOT_PROVIDER_API_KEY"); + expect(diagnostic).toContain("Copilot requests authentication failed"); + expect(diagnostic).toContain("https://github.github.com/gh-aw/reference/billing/"); + expect(diagnostic).not.toContain("COPILOT_PROVIDER_API_KEY"); + } finally { + if (typeof originalPromptsDir === "string") { + process.env.GH_AW_PROMPTS_DIR = originalPromptsDir; + } else { + delete process.env.GH_AW_PROMPTS_DIR; + } + } }); - it("falls back to inline 403 guidance when the template file is unavailable", () => { - const logger = vi.fn(); - const diagnostic = buildCopilotProxyAuthFailureDiagnostic( - "Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).", - { - COPILOT_MODEL: "claude-sonnet-4.5", - S2STOKENS: "true", - }, - { - renderTemplateFromFile: () => { - throw new Error("ENOENT: missing template"); + it("resolves the 403 guidance template from the runtime prompts directory", () => { + const originalPromptsDir = process.env.GH_AW_PROMPTS_DIR; + process.env.GH_AW_PROMPTS_DIR = "/tmp/runtime-prompts"; + try { + const renderTemplateFromFile = vi.fn().mockReturnValue("rendered"); + + const diagnostic = buildCopilotProxyAuthFailureDiagnostic( + "Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).", + { + COPILOT_MODEL: "claude-sonnet-4.5", + S2STOKENS: "true", }, - logger, - } - ); + { renderTemplateFromFile } + ); - expect(diagnostic).toContain("Copilot requests authentication failed"); - expect(diagnostic).toContain("HTTP 403"); - expect(diagnostic).toContain("model=claude-sonnet-4.5"); - expect(diagnostic).toContain("stage=starting the Copilot CLI request"); - expect(diagnostic).toContain("centralized Copilot billing"); - expect(logger).toHaveBeenCalledWith(expect.stringContaining("proxy auth diagnostic template unavailable")); + expect(diagnostic).toBe("rendered"); + expect(renderTemplateFromFile).toHaveBeenCalledWith("/tmp/runtime-prompts/copilot_requests_proxy_auth_403.md", { + selected_model: "claude-sonnet-4.5", + stage: "starting the Copilot CLI request", + }); + } finally { + if (typeof originalPromptsDir === "string") { + process.env.GH_AW_PROMPTS_DIR = originalPromptsDir; + } else { + delete process.env.GH_AW_PROMPTS_DIR; + } + } }); it("returns empty string for proxy 403 when S2STOKENS is not set (BYOK mode)", () => { From 9b93555a0ccb7bec872a76b7e77a5477a91f4da2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 14:51:17 +0000 Subject: [PATCH 05/13] Deduplicate prompt dir test setup Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/copilot_harness.test.cjs | 50 +++++++++-------------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/actions/setup/js/copilot_harness.test.cjs b/actions/setup/js/copilot_harness.test.cjs index 81934ab6989..a8e93b9bdbe 100644 --- a/actions/setup/js/copilot_harness.test.cjs +++ b/actions/setup/js/copilot_harness.test.cjs @@ -57,6 +57,20 @@ function makeHarnessTempDir(name) { return fs.mkdtempSync(path.join(agentTempDir, name)); } +function withPromptsDir(promptsDir, callback) { + const originalPromptsDir = process.env.GH_AW_PROMPTS_DIR; + process.env.GH_AW_PROMPTS_DIR = promptsDir; + try { + return callback(); + } finally { + if (typeof originalPromptsDir === "string") { + process.env.GH_AW_PROMPTS_DIR = originalPromptsDir; + } else { + delete process.env.GH_AW_PROMPTS_DIR; + } + } +} + describe("copilot_harness.cjs", () => { // Test the core logic patterns used by the driver without importing the module // (importing the module would invoke main() which calls process.exit). @@ -975,9 +989,7 @@ describe("copilot_harness.cjs", () => { }); it("rewrites local proxy 403 errors in copilot-requests mode to org-billing guidance", () => { - const originalPromptsDir = process.env.GH_AW_PROMPTS_DIR; - process.env.GH_AW_PROMPTS_DIR = promptsSourceDir; - try { + withPromptsDir(promptsSourceDir, () => { const diagnostic = buildCopilotProxyAuthFailureDiagnostic("Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).\nCheck your COPILOT_PROVIDER_API_KEY or COPILOT_PROVIDER_BEARER_TOKEN.", { COPILOT_MODEL: "claude-sonnet-4.5", S2STOKENS: "true", @@ -991,19 +1003,11 @@ describe("copilot_harness.cjs", () => { expect(diagnostic).toContain("centralized Copilot billing"); expect(diagnostic).toContain("https://github.github.com/gh-aw/reference/billing/"); expect(diagnostic).not.toContain("COPILOT_PROVIDER_API_KEY"); - } finally { - if (typeof originalPromptsDir === "string") { - process.env.GH_AW_PROMPTS_DIR = originalPromptsDir; - } else { - delete process.env.GH_AW_PROMPTS_DIR; - } - } + }); }); it("treats truthy S2STOKENS values as copilot-requests mode for 403 guidance", () => { - const originalPromptsDir = process.env.GH_AW_PROMPTS_DIR; - process.env.GH_AW_PROMPTS_DIR = promptsSourceDir; - try { + withPromptsDir(promptsSourceDir, () => { const diagnostic = buildCopilotProxyAuthFailureDiagnostic("Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).", { COPILOT_MODEL: "claude-sonnet-4.5", S2STOKENS: " YES ", @@ -1012,19 +1016,11 @@ describe("copilot_harness.cjs", () => { expect(diagnostic).toContain("Copilot requests authentication failed"); expect(diagnostic).toContain("https://github.github.com/gh-aw/reference/billing/"); expect(diagnostic).not.toContain("COPILOT_PROVIDER_API_KEY"); - } finally { - if (typeof originalPromptsDir === "string") { - process.env.GH_AW_PROMPTS_DIR = originalPromptsDir; - } else { - delete process.env.GH_AW_PROMPTS_DIR; - } - } + }); }); it("resolves the 403 guidance template from the runtime prompts directory", () => { - const originalPromptsDir = process.env.GH_AW_PROMPTS_DIR; - process.env.GH_AW_PROMPTS_DIR = "/tmp/runtime-prompts"; - try { + withPromptsDir("/tmp/runtime-prompts", () => { const renderTemplateFromFile = vi.fn().mockReturnValue("rendered"); const diagnostic = buildCopilotProxyAuthFailureDiagnostic( @@ -1041,13 +1037,7 @@ describe("copilot_harness.cjs", () => { selected_model: "claude-sonnet-4.5", stage: "starting the Copilot CLI request", }); - } finally { - if (typeof originalPromptsDir === "string") { - process.env.GH_AW_PROMPTS_DIR = originalPromptsDir; - } else { - delete process.env.GH_AW_PROMPTS_DIR; - } - } + }); }); it("returns empty string for proxy 403 when S2STOKENS is not set (BYOK mode)", () => { From d6e7634d0e21eff3323e93a3d25782ae6e06aa84 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 14:53:26 +0000 Subject: [PATCH 06/13] Scope prompt path fixture to tests Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/copilot_harness.test.cjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actions/setup/js/copilot_harness.test.cjs b/actions/setup/js/copilot_harness.test.cjs index a8e93b9bdbe..c1a43136eb5 100644 --- a/actions/setup/js/copilot_harness.test.cjs +++ b/actions/setup/js/copilot_harness.test.cjs @@ -8,7 +8,6 @@ import path from "path"; const require = createRequire(import.meta.url); const { EventEmitter } = require("events"); const { PassThrough } = require("stream"); -const promptsSourceDir = new URL("../md/", import.meta.url).pathname; const { buildCopilotSDKServerArgs, getCopilotSDKServerPort, startCopilotSDKServer, stopCopilotSDKServer, waitForCopilotSDKServer } = require("./copilot_sdk_sidecar.cjs"); const { buildCopilotSDKEnv, isCopilotSDKEnabled } = require("./process_runner.cjs"); const { @@ -974,6 +973,8 @@ describe("copilot_harness.cjs", () => { }); describe("gh-aw API proxy auth diagnostics", () => { + const promptsSourceDir = new URL("../md/", import.meta.url).pathname; + it("rewrites local proxy 401 errors to COPILOT_GITHUB_TOKEN guidance", () => { const diagnostic = buildCopilotProxyAuthFailureDiagnostic("Authentication failed with provider at http://172.30.0.30:10002 (HTTP 401).\nCheck your COPILOT_PROVIDER_API_KEY or COPILOT_PROVIDER_BEARER_TOKEN.", { COPILOT_MODEL: "claude-sonnet-4.5", From 6996c40f46ba5f24796c0482f535b9748930eb62 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 14:55:44 +0000 Subject: [PATCH 07/13] Make prompt path tests portable Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/copilot_harness.test.cjs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/actions/setup/js/copilot_harness.test.cjs b/actions/setup/js/copilot_harness.test.cjs index c1a43136eb5..fd9ec6ca346 100644 --- a/actions/setup/js/copilot_harness.test.cjs +++ b/actions/setup/js/copilot_harness.test.cjs @@ -973,7 +973,7 @@ describe("copilot_harness.cjs", () => { }); describe("gh-aw API proxy auth diagnostics", () => { - const promptsSourceDir = new URL("../md/", import.meta.url).pathname; + const promptsSourceDir = path.resolve("../md"); it("rewrites local proxy 401 errors to COPILOT_GITHUB_TOKEN guidance", () => { const diagnostic = buildCopilotProxyAuthFailureDiagnostic("Authentication failed with provider at http://172.30.0.30:10002 (HTTP 401).\nCheck your COPILOT_PROVIDER_API_KEY or COPILOT_PROVIDER_BEARER_TOKEN.", { @@ -1021,7 +1021,8 @@ describe("copilot_harness.cjs", () => { }); it("resolves the 403 guidance template from the runtime prompts directory", () => { - withPromptsDir("/tmp/runtime-prompts", () => { + const runtimePromptsDir = path.join(os.tmpdir(), "runtime-prompts"); + withPromptsDir(runtimePromptsDir, () => { const renderTemplateFromFile = vi.fn().mockReturnValue("rendered"); const diagnostic = buildCopilotProxyAuthFailureDiagnostic( @@ -1034,7 +1035,7 @@ describe("copilot_harness.cjs", () => { ); expect(diagnostic).toBe("rendered"); - expect(renderTemplateFromFile).toHaveBeenCalledWith("/tmp/runtime-prompts/copilot_requests_proxy_auth_403.md", { + expect(renderTemplateFromFile).toHaveBeenCalledWith(path.join(runtimePromptsDir, "copilot_requests_proxy_auth_403.md"), { selected_model: "claude-sonnet-4.5", stage: "starting the Copilot CLI request", }); From ccb067aba07aa82406ed6e9dcce90d1dac976181 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 14:58:08 +0000 Subject: [PATCH 08/13] Cover runtime prompt path resolution Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/copilot_harness.test.cjs | 47 ++++++++++++++++++++--- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/actions/setup/js/copilot_harness.test.cjs b/actions/setup/js/copilot_harness.test.cjs index fd9ec6ca346..27c877c19ed 100644 --- a/actions/setup/js/copilot_harness.test.cjs +++ b/actions/setup/js/copilot_harness.test.cjs @@ -56,7 +56,7 @@ function makeHarnessTempDir(name) { return fs.mkdtempSync(path.join(agentTempDir, name)); } -function withPromptsDir(promptsDir, callback) { +function withTestPromptsDir(promptsDir, callback) { const originalPromptsDir = process.env.GH_AW_PROMPTS_DIR; process.env.GH_AW_PROMPTS_DIR = promptsDir; try { @@ -990,7 +990,7 @@ describe("copilot_harness.cjs", () => { }); it("rewrites local proxy 403 errors in copilot-requests mode to org-billing guidance", () => { - withPromptsDir(promptsSourceDir, () => { + withTestPromptsDir(promptsSourceDir, () => { const diagnostic = buildCopilotProxyAuthFailureDiagnostic("Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).\nCheck your COPILOT_PROVIDER_API_KEY or COPILOT_PROVIDER_BEARER_TOKEN.", { COPILOT_MODEL: "claude-sonnet-4.5", S2STOKENS: "true", @@ -1008,7 +1008,7 @@ describe("copilot_harness.cjs", () => { }); it("treats truthy S2STOKENS values as copilot-requests mode for 403 guidance", () => { - withPromptsDir(promptsSourceDir, () => { + withTestPromptsDir(promptsSourceDir, () => { const diagnostic = buildCopilotProxyAuthFailureDiagnostic("Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).", { COPILOT_MODEL: "claude-sonnet-4.5", S2STOKENS: " YES ", @@ -1021,8 +1021,8 @@ describe("copilot_harness.cjs", () => { }); it("resolves the 403 guidance template from the runtime prompts directory", () => { - const runtimePromptsDir = path.join(os.tmpdir(), "runtime-prompts"); - withPromptsDir(runtimePromptsDir, () => { + const runtimePromptsDir = fs.mkdtempSync(path.join(os.tmpdir(), "runtime-prompts-")); + withTestPromptsDir(runtimePromptsDir, () => { const renderTemplateFromFile = vi.fn().mockReturnValue("rendered"); const diagnostic = buildCopilotProxyAuthFailureDiagnostic( @@ -1042,6 +1042,43 @@ describe("copilot_harness.cjs", () => { }); }); + it("resolves the 403 guidance template from RUNNER_TEMP when GH_AW_PROMPTS_DIR is unset", () => { + const originalPromptsDir = process.env.GH_AW_PROMPTS_DIR; + const originalRunnerTemp = process.env.RUNNER_TEMP; + const runnerTempDir = fs.mkdtempSync(path.join(os.tmpdir(), "runner-temp-")); + delete process.env.GH_AW_PROMPTS_DIR; + process.env.RUNNER_TEMP = runnerTempDir; + try { + const renderTemplateFromFile = vi.fn().mockReturnValue("rendered"); + + const diagnostic = buildCopilotProxyAuthFailureDiagnostic( + "Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).", + { + COPILOT_MODEL: "claude-sonnet-4.5", + S2STOKENS: "true", + }, + { renderTemplateFromFile } + ); + + expect(diagnostic).toBe("rendered"); + expect(renderTemplateFromFile).toHaveBeenCalledWith(path.join(runnerTempDir, "gh-aw", "prompts", "copilot_requests_proxy_auth_403.md"), { + selected_model: "claude-sonnet-4.5", + stage: "starting the Copilot CLI request", + }); + } finally { + if (typeof originalPromptsDir === "string") { + process.env.GH_AW_PROMPTS_DIR = originalPromptsDir; + } else { + delete process.env.GH_AW_PROMPTS_DIR; + } + if (typeof originalRunnerTemp === "string") { + process.env.RUNNER_TEMP = originalRunnerTemp; + } else { + delete process.env.RUNNER_TEMP; + } + } + }); + it("returns empty string for proxy 403 when S2STOKENS is not set (BYOK mode)", () => { const diagnostic = buildCopilotProxyAuthFailureDiagnostic("Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).", { COPILOT_MODEL: "claude-sonnet-4.5", From 26c99dddcb0d1c8ab68cfed87dab928086cb7b5a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 15:00:23 +0000 Subject: [PATCH 09/13] Clean up prompt path temp dirs Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/copilot_harness.test.cjs | 39 +++++++++++++---------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/actions/setup/js/copilot_harness.test.cjs b/actions/setup/js/copilot_harness.test.cjs index 27c877c19ed..c6e0ff5987c 100644 --- a/actions/setup/js/copilot_harness.test.cjs +++ b/actions/setup/js/copilot_harness.test.cjs @@ -1022,24 +1022,28 @@ describe("copilot_harness.cjs", () => { it("resolves the 403 guidance template from the runtime prompts directory", () => { const runtimePromptsDir = fs.mkdtempSync(path.join(os.tmpdir(), "runtime-prompts-")); - withTestPromptsDir(runtimePromptsDir, () => { - const renderTemplateFromFile = vi.fn().mockReturnValue("rendered"); - - const diagnostic = buildCopilotProxyAuthFailureDiagnostic( - "Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).", - { - COPILOT_MODEL: "claude-sonnet-4.5", - S2STOKENS: "true", - }, - { renderTemplateFromFile } - ); - - expect(diagnostic).toBe("rendered"); - expect(renderTemplateFromFile).toHaveBeenCalledWith(path.join(runtimePromptsDir, "copilot_requests_proxy_auth_403.md"), { - selected_model: "claude-sonnet-4.5", - stage: "starting the Copilot CLI request", + try { + withTestPromptsDir(runtimePromptsDir, () => { + const renderTemplateFromFile = vi.fn().mockReturnValue("rendered"); + + const diagnostic = buildCopilotProxyAuthFailureDiagnostic( + "Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).", + { + COPILOT_MODEL: "claude-sonnet-4.5", + S2STOKENS: "true", + }, + { renderTemplateFromFile } + ); + + expect(diagnostic).toBe("rendered"); + expect(renderTemplateFromFile).toHaveBeenCalledWith(path.join(runtimePromptsDir, "copilot_requests_proxy_auth_403.md"), { + selected_model: "claude-sonnet-4.5", + stage: "starting the Copilot CLI request", + }); }); - }); + } finally { + fs.rmSync(runtimePromptsDir, { recursive: true, force: true }); + } }); it("resolves the 403 guidance template from RUNNER_TEMP when GH_AW_PROMPTS_DIR is unset", () => { @@ -1076,6 +1080,7 @@ describe("copilot_harness.cjs", () => { } else { delete process.env.RUNNER_TEMP; } + fs.rmSync(runnerTempDir, { recursive: true, force: true }); } }); From f7264e3afe7857d4f8e84378b6bec3e87f5505b5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 15:03:42 +0000 Subject: [PATCH 10/13] Exercise real prompt template resolution Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/copilot_harness.test.cjs | 65 +++++++++++------------ 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/actions/setup/js/copilot_harness.test.cjs b/actions/setup/js/copilot_harness.test.cjs index c6e0ff5987c..0c335ffa661 100644 --- a/actions/setup/js/copilot_harness.test.cjs +++ b/actions/setup/js/copilot_harness.test.cjs @@ -70,6 +70,20 @@ function withTestPromptsDir(promptsDir, callback) { } } +function withRunnerTemp(runnerTempDir, callback) { + const originalRunnerTemp = process.env.RUNNER_TEMP; + process.env.RUNNER_TEMP = runnerTempDir; + try { + return callback(); + } finally { + if (typeof originalRunnerTemp === "string") { + process.env.RUNNER_TEMP = originalRunnerTemp; + } else { + delete process.env.RUNNER_TEMP; + } + } +} + describe("copilot_harness.cjs", () => { // Test the core logic patterns used by the driver without importing the module // (importing the module would invoke main() which calls process.exit). @@ -1023,23 +1037,16 @@ describe("copilot_harness.cjs", () => { it("resolves the 403 guidance template from the runtime prompts directory", () => { const runtimePromptsDir = fs.mkdtempSync(path.join(os.tmpdir(), "runtime-prompts-")); try { + fs.copyFileSync(path.join(promptsSourceDir, "copilot_requests_proxy_auth_403.md"), path.join(runtimePromptsDir, "copilot_requests_proxy_auth_403.md")); withTestPromptsDir(runtimePromptsDir, () => { - const renderTemplateFromFile = vi.fn().mockReturnValue("rendered"); - - const diagnostic = buildCopilotProxyAuthFailureDiagnostic( - "Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).", - { - COPILOT_MODEL: "claude-sonnet-4.5", - S2STOKENS: "true", - }, - { renderTemplateFromFile } - ); - - expect(diagnostic).toBe("rendered"); - expect(renderTemplateFromFile).toHaveBeenCalledWith(path.join(runtimePromptsDir, "copilot_requests_proxy_auth_403.md"), { - selected_model: "claude-sonnet-4.5", - stage: "starting the Copilot CLI request", + const diagnostic = buildCopilotProxyAuthFailureDiagnostic("Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).", { + COPILOT_MODEL: "claude-sonnet-4.5", + S2STOKENS: "true", }); + + expect(diagnostic).toContain("Copilot requests authentication failed"); + expect(diagnostic).toContain("model=claude-sonnet-4.5"); + expect(diagnostic).toContain("stage=starting the Copilot CLI request"); }); } finally { fs.rmSync(runtimePromptsDir, { recursive: true, force: true }); @@ -1048,26 +1055,21 @@ describe("copilot_harness.cjs", () => { it("resolves the 403 guidance template from RUNNER_TEMP when GH_AW_PROMPTS_DIR is unset", () => { const originalPromptsDir = process.env.GH_AW_PROMPTS_DIR; - const originalRunnerTemp = process.env.RUNNER_TEMP; const runnerTempDir = fs.mkdtempSync(path.join(os.tmpdir(), "runner-temp-")); delete process.env.GH_AW_PROMPTS_DIR; - process.env.RUNNER_TEMP = runnerTempDir; try { - const renderTemplateFromFile = vi.fn().mockReturnValue("rendered"); - - const diagnostic = buildCopilotProxyAuthFailureDiagnostic( - "Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).", - { + const promptsDir = path.join(runnerTempDir, "gh-aw", "prompts"); + fs.mkdirSync(promptsDir, { recursive: true }); + fs.copyFileSync(path.join(promptsSourceDir, "copilot_requests_proxy_auth_403.md"), path.join(promptsDir, "copilot_requests_proxy_auth_403.md")); + withRunnerTemp(runnerTempDir, () => { + const diagnostic = buildCopilotProxyAuthFailureDiagnostic("Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).", { COPILOT_MODEL: "claude-sonnet-4.5", S2STOKENS: "true", - }, - { renderTemplateFromFile } - ); + }); - expect(diagnostic).toBe("rendered"); - expect(renderTemplateFromFile).toHaveBeenCalledWith(path.join(runnerTempDir, "gh-aw", "prompts", "copilot_requests_proxy_auth_403.md"), { - selected_model: "claude-sonnet-4.5", - stage: "starting the Copilot CLI request", + expect(diagnostic).toContain("Copilot requests authentication failed"); + expect(diagnostic).toContain("model=claude-sonnet-4.5"); + expect(diagnostic).toContain("stage=starting the Copilot CLI request"); }); } finally { if (typeof originalPromptsDir === "string") { @@ -1075,11 +1077,6 @@ describe("copilot_harness.cjs", () => { } else { delete process.env.GH_AW_PROMPTS_DIR; } - if (typeof originalRunnerTemp === "string") { - process.env.RUNNER_TEMP = originalRunnerTemp; - } else { - delete process.env.RUNNER_TEMP; - } fs.rmSync(runnerTempDir, { recursive: true, force: true }); } }); From 225b5147b41c5c3014c5c32473496fadd56d050a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 15:06:11 +0000 Subject: [PATCH 11/13] Deduplicate prompt env cleanup Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/copilot_harness.test.cjs | 31 +++++++++++------------ 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/actions/setup/js/copilot_harness.test.cjs b/actions/setup/js/copilot_harness.test.cjs index 0c335ffa661..5dba63ee0ec 100644 --- a/actions/setup/js/copilot_harness.test.cjs +++ b/actions/setup/js/copilot_harness.test.cjs @@ -58,7 +58,11 @@ function makeHarnessTempDir(name) { function withTestPromptsDir(promptsDir, callback) { const originalPromptsDir = process.env.GH_AW_PROMPTS_DIR; - process.env.GH_AW_PROMPTS_DIR = promptsDir; + if (typeof promptsDir === "string") { + process.env.GH_AW_PROMPTS_DIR = promptsDir; + } else { + delete process.env.GH_AW_PROMPTS_DIR; + } try { return callback(); } finally { @@ -1054,29 +1058,24 @@ describe("copilot_harness.cjs", () => { }); it("resolves the 403 guidance template from RUNNER_TEMP when GH_AW_PROMPTS_DIR is unset", () => { - const originalPromptsDir = process.env.GH_AW_PROMPTS_DIR; const runnerTempDir = fs.mkdtempSync(path.join(os.tmpdir(), "runner-temp-")); - delete process.env.GH_AW_PROMPTS_DIR; try { const promptsDir = path.join(runnerTempDir, "gh-aw", "prompts"); fs.mkdirSync(promptsDir, { recursive: true }); fs.copyFileSync(path.join(promptsSourceDir, "copilot_requests_proxy_auth_403.md"), path.join(promptsDir, "copilot_requests_proxy_auth_403.md")); - withRunnerTemp(runnerTempDir, () => { - const diagnostic = buildCopilotProxyAuthFailureDiagnostic("Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).", { - COPILOT_MODEL: "claude-sonnet-4.5", - S2STOKENS: "true", + withTestPromptsDir(undefined, () => { + withRunnerTemp(runnerTempDir, () => { + const diagnostic = buildCopilotProxyAuthFailureDiagnostic("Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).", { + COPILOT_MODEL: "claude-sonnet-4.5", + S2STOKENS: "true", + }); + + expect(diagnostic).toContain("Copilot requests authentication failed"); + expect(diagnostic).toContain("model=claude-sonnet-4.5"); + expect(diagnostic).toContain("stage=starting the Copilot CLI request"); }); - - expect(diagnostic).toContain("Copilot requests authentication failed"); - expect(diagnostic).toContain("model=claude-sonnet-4.5"); - expect(diagnostic).toContain("stage=starting the Copilot CLI request"); }); } finally { - if (typeof originalPromptsDir === "string") { - process.env.GH_AW_PROMPTS_DIR = originalPromptsDir; - } else { - delete process.env.GH_AW_PROMPTS_DIR; - } fs.rmSync(runnerTempDir, { recursive: true, force: true }); } }); From 3473c08cae2df488272f15b77dd0f20f5010d787 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 15:08:50 +0000 Subject: [PATCH 12/13] Share prompt template test setup Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/copilot_harness.test.cjs | 70 +++++++++++++---------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/actions/setup/js/copilot_harness.test.cjs b/actions/setup/js/copilot_harness.test.cjs index 5dba63ee0ec..cecda753eb3 100644 --- a/actions/setup/js/copilot_harness.test.cjs +++ b/actions/setup/js/copilot_harness.test.cjs @@ -88,6 +88,18 @@ function withRunnerTemp(runnerTempDir, callback) { } } +function withTemporaryPromptTemplate(prefix, promptDirResolver, callback) { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), prefix)); + try { + const promptsDir = promptDirResolver(tempDir); + fs.mkdirSync(promptsDir, { recursive: true }); + fs.copyFileSync(path.join(path.resolve("../md"), "copilot_requests_proxy_auth_403.md"), path.join(promptsDir, "copilot_requests_proxy_auth_403.md")); + return callback(tempDir, promptsDir); + } finally { + fs.rmSync(tempDir, { recursive: true, force: true }); + } +} + describe("copilot_harness.cjs", () => { // Test the core logic patterns used by the driver without importing the module // (importing the module would invoke main() which calls process.exit). @@ -1039,32 +1051,11 @@ describe("copilot_harness.cjs", () => { }); it("resolves the 403 guidance template from the runtime prompts directory", () => { - const runtimePromptsDir = fs.mkdtempSync(path.join(os.tmpdir(), "runtime-prompts-")); - try { - fs.copyFileSync(path.join(promptsSourceDir, "copilot_requests_proxy_auth_403.md"), path.join(runtimePromptsDir, "copilot_requests_proxy_auth_403.md")); - withTestPromptsDir(runtimePromptsDir, () => { - const diagnostic = buildCopilotProxyAuthFailureDiagnostic("Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).", { - COPILOT_MODEL: "claude-sonnet-4.5", - S2STOKENS: "true", - }); - - expect(diagnostic).toContain("Copilot requests authentication failed"); - expect(diagnostic).toContain("model=claude-sonnet-4.5"); - expect(diagnostic).toContain("stage=starting the Copilot CLI request"); - }); - } finally { - fs.rmSync(runtimePromptsDir, { recursive: true, force: true }); - } - }); - - it("resolves the 403 guidance template from RUNNER_TEMP when GH_AW_PROMPTS_DIR is unset", () => { - const runnerTempDir = fs.mkdtempSync(path.join(os.tmpdir(), "runner-temp-")); - try { - const promptsDir = path.join(runnerTempDir, "gh-aw", "prompts"); - fs.mkdirSync(promptsDir, { recursive: true }); - fs.copyFileSync(path.join(promptsSourceDir, "copilot_requests_proxy_auth_403.md"), path.join(promptsDir, "copilot_requests_proxy_auth_403.md")); - withTestPromptsDir(undefined, () => { - withRunnerTemp(runnerTempDir, () => { + withTemporaryPromptTemplate( + "runtime-prompts-", + tempDir => tempDir, + (_tempDir, runtimePromptsDir) => { + withTestPromptsDir(runtimePromptsDir, () => { const diagnostic = buildCopilotProxyAuthFailureDiagnostic("Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).", { COPILOT_MODEL: "claude-sonnet-4.5", S2STOKENS: "true", @@ -1074,10 +1065,29 @@ describe("copilot_harness.cjs", () => { expect(diagnostic).toContain("model=claude-sonnet-4.5"); expect(diagnostic).toContain("stage=starting the Copilot CLI request"); }); - }); - } finally { - fs.rmSync(runnerTempDir, { recursive: true, force: true }); - } + } + ); + }); + + it("resolves the 403 guidance template from RUNNER_TEMP when GH_AW_PROMPTS_DIR is unset", () => { + withTemporaryPromptTemplate( + "runner-temp-", + tempDir => path.join(tempDir, "gh-aw", "prompts"), + runnerTempDir => { + withTestPromptsDir(undefined, () => { + withRunnerTemp(runnerTempDir, () => { + const diagnostic = buildCopilotProxyAuthFailureDiagnostic("Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).", { + COPILOT_MODEL: "claude-sonnet-4.5", + S2STOKENS: "true", + }); + + expect(diagnostic).toContain("Copilot requests authentication failed"); + expect(diagnostic).toContain("model=claude-sonnet-4.5"); + expect(diagnostic).toContain("stage=starting the Copilot CLI request"); + }); + }); + } + ); }); it("returns empty string for proxy 403 when S2STOKENS is not set (BYOK mode)", () => { From 9817f04d30e580df32421a8ba5ddcd08d970bf0e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 15:11:33 +0000 Subject: [PATCH 13/13] Tighten prompt path integration tests Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/copilot_harness.test.cjs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/actions/setup/js/copilot_harness.test.cjs b/actions/setup/js/copilot_harness.test.cjs index cecda753eb3..747062783c4 100644 --- a/actions/setup/js/copilot_harness.test.cjs +++ b/actions/setup/js/copilot_harness.test.cjs @@ -88,12 +88,12 @@ function withRunnerTemp(runnerTempDir, callback) { } } -function withTemporaryPromptTemplate(prefix, promptDirResolver, callback) { +function withTemporaryPromptTemplate(prefix, sourceTemplateDir, promptDirResolver, callback) { const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), prefix)); try { const promptsDir = promptDirResolver(tempDir); fs.mkdirSync(promptsDir, { recursive: true }); - fs.copyFileSync(path.join(path.resolve("../md"), "copilot_requests_proxy_auth_403.md"), path.join(promptsDir, "copilot_requests_proxy_auth_403.md")); + fs.copyFileSync(path.join(sourceTemplateDir, "copilot_requests_proxy_auth_403.md"), path.join(promptsDir, "copilot_requests_proxy_auth_403.md")); return callback(tempDir, promptsDir); } finally { fs.rmSync(tempDir, { recursive: true, force: true }); @@ -1053,17 +1053,29 @@ describe("copilot_harness.cjs", () => { it("resolves the 403 guidance template from the runtime prompts directory", () => { withTemporaryPromptTemplate( "runtime-prompts-", + promptsSourceDir, tempDir => tempDir, (_tempDir, runtimePromptsDir) => { withTestPromptsDir(runtimePromptsDir, () => { - const diagnostic = buildCopilotProxyAuthFailureDiagnostic("Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).", { - COPILOT_MODEL: "claude-sonnet-4.5", - S2STOKENS: "true", + const renderTemplateFromFile = vi.fn((templatePath, context) => { + return fs.readFileSync(templatePath, "utf8").replace("{selected_model}", context.selected_model).replace("{stage}", context.stage); }); + const diagnostic = buildCopilotProxyAuthFailureDiagnostic( + "Authentication failed with provider at http://172.30.0.30:10002 (HTTP 403).", + { + COPILOT_MODEL: "claude-sonnet-4.5", + S2STOKENS: "true", + }, + { renderTemplateFromFile } + ); expect(diagnostic).toContain("Copilot requests authentication failed"); expect(diagnostic).toContain("model=claude-sonnet-4.5"); expect(diagnostic).toContain("stage=starting the Copilot CLI request"); + expect(renderTemplateFromFile).toHaveBeenCalledWith(path.join(runtimePromptsDir, "copilot_requests_proxy_auth_403.md"), { + selected_model: "claude-sonnet-4.5", + stage: "starting the Copilot CLI request", + }); }); } ); @@ -1072,6 +1084,7 @@ describe("copilot_harness.cjs", () => { it("resolves the 403 guidance template from RUNNER_TEMP when GH_AW_PROMPTS_DIR is unset", () => { withTemporaryPromptTemplate( "runner-temp-", + promptsSourceDir, tempDir => path.join(tempDir, "gh-aw", "prompts"), runnerTempDir => { withTestPromptsDir(undefined, () => {