Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions actions/setup/js/checkout_pr_branch.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
*/

const { getErrorMessage } = require("./error_helpers.cjs");
const { renderTemplateFromFile } = require("./messages_core.cjs");
const { renderTemplateFromFile, getPromptPath } = require("./messages_core.cjs");
const { detectForkPR } = require("./pr_helpers.cjs");
const { ERR_API } = require("./error_codes.cjs");

Expand Down Expand Up @@ -323,7 +323,7 @@ Pull request #${pullRequest.number} was merged after this workflow was triggered
core.setOutput("checkout_pr_success", "false");

// Load and render step summary template
const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/pr_checkout_failure.md`;
const templatePath = getPromptPath("pr_checkout_failure.md");
const summaryContent = renderTemplateFromFile(templatePath, {
error_message: errorMsg,
});
Expand Down
1 change: 1 addition & 0 deletions actions/setup/js/checkout_pr_branch.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ describe("checkout_pr_branch.cjs", () => {
}
if (module === "./messages_core.cjs") {
return {
getPromptPath: name => `/mock/prompts/${name}`,
renderTemplateFromFile: (templatePath, context) => {
const template = mockRequire("fs").readFileSync(templatePath, "utf8");
return template.replace(/\{(\w+)\}/g, (match, key) => {
Expand Down
3 changes: 2 additions & 1 deletion actions/setup/js/create_missing_data_issue.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/// <reference types="@actions/github-script" />

const { buildMissingIssueHandler } = require("./missing_issue_helpers.cjs");
const { getPromptPath } = require("./messages_core.cjs");

/**
* @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction
Expand All @@ -20,7 +21,7 @@ const main = buildMissingIssueHandler({
defaultTitlePrefix: "[missing data]",
defaultLabels: ["agentic-workflows"],
itemsField: "missing_data",
templatePath: `${process.env.RUNNER_TEMP}/gh-aw/prompts/missing_data_issue.md`,
templatePath: getPromptPath("missing_data_issue.md"),
templateListKey: "missing_data_list",
buildCommentHeader: runUrl => [`## Missing Data Reported`, ``, `The following data was reported as missing during [workflow run](${runUrl}):`, ``],
renderCommentItem: (item, index) => {
Expand Down
3 changes: 2 additions & 1 deletion actions/setup/js/create_missing_tool_issue.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/// <reference types="@actions/github-script" />

const { buildMissingIssueHandler } = require("./missing_issue_helpers.cjs");
const { getPromptPath } = require("./messages_core.cjs");

/**
* @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction
Expand All @@ -20,7 +21,7 @@ const main = buildMissingIssueHandler({
defaultTitlePrefix: "[missing tool]",
defaultLabels: ["agentic-workflows"],
itemsField: "missing_tools",
templatePath: `${process.env.RUNNER_TEMP}/gh-aw/prompts/missing_tool_issue.md`,
templatePath: getPromptPath("missing_tool_issue.md"),
templateListKey: "missing_tools_list",
buildCommentHeader: runUrl => [`## Missing Tools Reported`, ``, `The following tools were reported as missing during [workflow run](${runUrl}):`, ``],
renderCommentItem: (tool, index) => {
Expand Down
8 changes: 4 additions & 4 deletions actions/setup/js/create_pull_request.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const { getBaseBranch } = require("./get_base_branch.cjs");
const { createAuthenticatedGitHubClient } = require("./handler_auth.cjs");
const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs");
const { checkFileProtection } = require("./manifest_file_helpers.cjs");
const { renderTemplateFromFile, buildProtectedFileList, encodePathSegments } = require("./messages_core.cjs");
const { renderTemplateFromFile, buildProtectedFileList, encodePathSegments, getPromptPath } = require("./messages_core.cjs");
const { COPILOT_REVIEWER_BOT, FAQ_CREATE_PR_PERMISSIONS_URL, MAX_ASSIGNEES } = require("./constants.cjs");
const { isStagedMode } = require("./safe_output_helpers.cjs");
const { withRetry, isTransientError } = require("./error_recovery.cjs");
Expand Down Expand Up @@ -1617,7 +1617,7 @@ ${patchPreview}`;
// Use the push-failed template with artifact download instructions.
const runId = context.runId;
const patchFileName = patchFilePath ? patchFilePath.replace("/tmp/gh-aw/", "") : "aw-unknown.patch";
const pushFailedTemplatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/manifest_protection_push_failed_fallback.md`;
const pushFailedTemplatePath = getPromptPath("manifest_protection_push_failed_fallback.md");
fallbackBody = renderTemplateFromFile(pushFailedTemplatePath, {
main_body: mainBodyContent,
footer: footerContent,
Expand All @@ -1634,7 +1634,7 @@ ${patchPreview}`;
const encodedBase = encodePathSegments(baseBranch);
const encodedHead = encodePathSegments(branchName);
const createPrUrl = `${githubServer}/${repoParts.owner}/${repoParts.repo}/compare/${encodedBase}...${encodedHead}?expand=1&title=${encodeURIComponent(title)}`;
const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/manifest_protection_create_pr_fallback.md`;
const templatePath = getPromptPath("manifest_protection_create_pr_fallback.md");
fallbackBody = renderTemplateFromFile(templatePath, {
main_body: mainBodyContent,
footer: footerContent,
Expand Down Expand Up @@ -1834,7 +1834,7 @@ ${patchPreview}`;
patchPreview = generatePatchPreview(patchContent);
}

const fallbackTemplatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/pr_permission_denied_fallback.md`;
const fallbackTemplatePath = getPromptPath("pr_permission_denied_fallback.md");
const fallbackBody = renderTemplateFromFile(fallbackTemplatePath, {
body,
branch_name: branchName,
Expand Down
3 changes: 2 additions & 1 deletion actions/setup/js/create_report_incomplete_issue.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/// <reference types="@actions/github-script" />

const { buildMissingIssueHandler } = require("./missing_issue_helpers.cjs");
const { getPromptPath } = require("./messages_core.cjs");

/**
* @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction
Expand All @@ -22,7 +23,7 @@ const main = buildMissingIssueHandler({
defaultTitlePrefix: "[incomplete]",
defaultLabels: ["agentic-workflows"],
itemsField: "incomplete_signals",
templatePath: `${process.env.RUNNER_TEMP}/gh-aw/prompts/missing_tool_issue.md`,
templatePath: getPromptPath("missing_tool_issue.md"),
templateListKey: "incomplete_signals_list",
buildCommentHeader: runUrl => [`## Incomplete Run Reported`, ``, `The agent reported that the task could not be completed during [workflow run](${runUrl}):`, ``],
renderCommentItem: (item, index) => {
Expand Down
7 changes: 4 additions & 3 deletions actions/setup/js/firewall_blocked_domains.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
const fs = require("fs");
const path = require("path");
const { sanitizeDomainName } = require("./sanitize_content_core.cjs");
const { renderTemplateFromFile } = require("./messages_core.cjs");
const { renderTemplateFromFile, getPromptPath } = require("./messages_core.cjs");
const { renderMarkdownTemplate } = require("./render_template.cjs");

/**
Expand Down Expand Up @@ -207,10 +207,11 @@ function generateBlockedDomainsSection(blockedDomains, templatePath) {

const hasGitHubApiBlocked = blockedDomains.includes("api.github.com");

// Resolve template path: explicit > RUNNER_TEMP (production) > source tree (local dev/test)
// Resolve template path: explicit > GH_AW_PROMPTS_DIR / RUNNER_TEMP (production) > source tree (local dev/test)
let resolvedTemplatePath = templatePath;
if (!resolvedTemplatePath) {
resolvedTemplatePath = process.env.RUNNER_TEMP ? `${process.env.RUNNER_TEMP}/gh-aw/prompts/firewall_blocked_domains.md` : path.join(__dirname, "../md/firewall_blocked_domains.md");
const hasRuntimePromptsDir = !!(process.env.RUNNER_TEMP || process.env.GH_AW_PROMPTS_DIR);
resolvedTemplatePath = hasRuntimePromptsDir ? getPromptPath("firewall_blocked_domains.md") : path.join(__dirname, "../md/firewall_blocked_domains.md");
}

// First pass: substitute {key} placeholders.
Expand Down
24 changes: 12 additions & 12 deletions actions/setup/js/handle_agent_failure.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
const { getErrorMessage } = require("./error_helpers.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");
const { getDetectionCautionAlert, getFooterAgentFailureIssueMessage, getFooterAgentFailureCommentMessage, generateXMLMarker } = require("./messages.cjs");
const { renderTemplate, renderTemplateFromFile } = require("./messages_core.cjs");
const { renderTemplate, renderTemplateFromFile, getPromptPath } = require("./messages_core.cjs");
const { getCurrentBranch } = require("./get_current_branch.cjs");
const { createExpirationLine, generateFooterWithExpiration } = require("./ephemerals.cjs");
const { MAX_SUB_ISSUES, getSubIssueCount } = require("./sub_issue_helpers.cjs");
Expand Down Expand Up @@ -618,7 +618,7 @@ function buildMissingDataContext(cacheMemoryEnabled) {
const hasCacheMiss = missingDataMessages.some(m => m.reason === "cache_memory_miss");
if (cacheMemoryEnabled && hasCacheMiss) {
core.info("Cache-miss detected despite cache-memory being available — likely a configuration problem");
const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/cache_memory_miss.md`;
const templatePath = getPromptPath("cache_memory_miss.md");
context += "\n" + renderTemplateFromFile(templatePath, {}) + "\n";
}

Expand Down Expand Up @@ -697,7 +697,7 @@ function buildTimeoutContext(isTimedOut, timeoutMinutes) {
const currentMinutes = parseInt(timeoutMinutes || "20", 10);
const suggestedMinutes = currentMinutes + 10;

const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/agent_timeout.md`;
const templatePath = getPromptPath("agent_timeout.md");
return "\n" + renderTemplateFromFile(templatePath, { current_minutes: currentMinutes, suggested_minutes: suggestedMinutes });
}

Expand All @@ -711,7 +711,7 @@ function buildInferenceAccessErrorContext(hasInferenceAccessError) {
return "";
}

const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/inference_access_error.md`;
const templatePath = getPromptPath("inference_access_error.md");
const template = fs.readFileSync(templatePath, "utf8");
return "\n" + template;
}
Expand All @@ -727,7 +727,7 @@ function buildMCPPolicyErrorContext(hasMCPPolicyError) {
return "";
}

const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/mcp_policy_error.md`;
const templatePath = getPromptPath("mcp_policy_error.md");
try {
const template = fs.readFileSync(templatePath, "utf8");
return "\n" + template;
Expand All @@ -752,7 +752,7 @@ function buildModelNotSupportedErrorContext(hasModelNotSupportedError) {
return "";
}

const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/model_not_supported_error.md`;
const templatePath = getPromptPath("model_not_supported_error.md");
try {
const template = fs.readFileSync(templatePath, "utf8");
return "\n" + template;
Expand Down Expand Up @@ -790,7 +790,7 @@ function buildLockdownCheckFailedContext(hasLockdownCheckFailed) {
return "";
}

const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/lockdown_check_failed.md`;
const templatePath = getPromptPath("lockdown_check_failed.md");
const template = fs.readFileSync(templatePath, "utf8");
return "\n" + template;
}
Expand All @@ -807,7 +807,7 @@ function buildStaleLockFileFailedContext(hasStaleLockFileFailed) {
return "";
}

const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/stale_lock_file_failed.md`;
const templatePath = getPromptPath("stale_lock_file_failed.md");
const template = fs.readFileSync(templatePath, "utf8");
return "\n" + template;
}
Expand Down Expand Up @@ -837,7 +837,7 @@ function buildAssignCopilotFailureContext(hasAssignCopilotFailures, assignCopilo
}
}

const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/assign_copilot_to_created_issues_failure.md`;
const templatePath = getPromptPath("assign_copilot_to_created_issues_failure.md");
return "\n" + renderTemplateFromFile(templatePath, { issues: issueList });
}

Expand Down Expand Up @@ -915,7 +915,7 @@ function buildEngineFailureContext() {
const hasCyberPolicyViolation = Array.from(errorMessages).some(msg => msg.includes("cyber_policy_violation"));
if (hasCyberPolicyViolation) {
core.info("Detected cyber_policy_violation error — using dedicated context message");
const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/cyber_policy_violation.md`;
const templatePath = getPromptPath("cyber_policy_violation.md");
try {
return "\n" + renderTemplateFromFile(templatePath, {});
} catch {
Expand Down Expand Up @@ -1263,7 +1263,7 @@ async function main() {
core.info(`Found existing issue #${existingIssue.number}: ${existingIssue.html_url}`);

// Read comment template
const commentTemplatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/agent_failure_comment.md`;
const commentTemplatePath = getPromptPath("agent_failure_comment.md");
const commentTemplate = fs.readFileSync(commentTemplatePath, "utf8");

// Extract run ID from URL (e.g., https://github.com/owner/repo/actions/runs/123 -> "123")
Expand Down Expand Up @@ -1428,7 +1428,7 @@ async function main() {
core.info("No existing issue found, creating a new one");

// Read issue template
const issueTemplatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/agent_failure_issue.md`;
const issueTemplatePath = getPromptPath("agent_failure_issue.md");
const issueTemplate = fs.readFileSync(issueTemplatePath, "utf8");

// Get current branch information
Expand Down
6 changes: 6 additions & 0 deletions actions/setup/js/handle_agent_failure.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ describe("handle_agent_failure", () => {

beforeEach(() => {
vi.resetModules();
process.env.RUNNER_TEMP = "/nonexistent";
// Stub readFileSync so the runtime path resolves to the source-tree template
fs.readFileSync = (filePath, encoding) => {
if (typeof filePath === "string" && filePath.includes("lockdown_check_failed.md")) {
Expand All @@ -517,6 +518,7 @@ describe("handle_agent_failure", () => {

afterEach(() => {
fs.readFileSync = originalReadFileSync;
delete process.env.RUNNER_TEMP;
});

it("returns empty string when no failure", () => {
Expand Down Expand Up @@ -553,6 +555,7 @@ describe("handle_agent_failure", () => {

beforeEach(() => {
vi.resetModules();
process.env.RUNNER_TEMP = "/nonexistent";
fs.readFileSync = (filePath, encoding) => {
if (typeof filePath === "string" && filePath.includes("stale_lock_file_failed.md")) {
return templateContent;
Expand All @@ -564,6 +567,7 @@ describe("handle_agent_failure", () => {

afterEach(() => {
fs.readFileSync = originalReadFileSync;
delete process.env.RUNNER_TEMP;
});

it("returns empty string when check did not fail", () => {
Expand Down Expand Up @@ -605,6 +609,7 @@ describe("handle_agent_failure", () => {

beforeEach(() => {
vi.resetModules();
process.env.RUNNER_TEMP = "/nonexistent";
// 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")) {
Expand All @@ -617,6 +622,7 @@ describe("handle_agent_failure", () => {

afterEach(() => {
fs.readFileSync = originalReadFileSync;
delete process.env.RUNNER_TEMP;
});

it("returns empty string when not timed out", () => {
Expand Down
6 changes: 3 additions & 3 deletions actions/setup/js/handle_detection_runs.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const { getErrorMessage } = require("./error_helpers.cjs");
const { ERR_API } = require("./error_codes.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");
const { generateFooterWithExpiration } = require("./ephemerals.cjs");
const { renderTemplateFromFile } = require("./messages_core.cjs");
const { renderTemplateFromFile, getPromptPath } = require("./messages_core.cjs");
const { getEffectiveTokensSuffix } = require("./effective_tokens.cjs");

/**
Expand Down Expand Up @@ -46,7 +46,7 @@ async function ensureDetectionRunsIssue() {
core.info(`No detection runs issue found, creating one`);

// Load template from file
const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/detection_runs_issue.md`;
const templatePath = getPromptPath("detection_runs_issue.md");
const parentBodyContent = fs.readFileSync(templatePath, "utf8");

const parentBody = generateFooterWithExpiration({
Expand Down Expand Up @@ -107,7 +107,7 @@ async function main() {
}

// Load and render comment template from file
const commentTemplatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/detection_runs_comment.md`;
const commentTemplatePath = getPromptPath("detection_runs_comment.md");

// Compute effective tokens suffix from environment variable (set by parse_token_usage.cjs / parse_mcp_gateway_log.cjs)
const effectiveTokensSuffix = getEffectiveTokensSuffix();
Expand Down
6 changes: 3 additions & 3 deletions actions/setup/js/handle_noop_message.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const { getErrorMessage } = require("./error_helpers.cjs");
const { ERR_API } = require("./error_codes.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");
const { generateFooterWithExpiration } = require("./ephemerals.cjs");
const { renderTemplateFromFile } = require("./messages_core.cjs");
const { renderTemplateFromFile, getPromptPath } = require("./messages_core.cjs");
const { loadAgentOutput } = require("./load_agent_output.cjs");
const { isStagedMode } = require("./safe_output_helpers.cjs");
const { getEffectiveTokensSuffix } = require("./effective_tokens.cjs");
Expand Down Expand Up @@ -48,7 +48,7 @@ async function ensureAgentRunsIssue() {
core.info(`No no-op runs issue found, creating one`);

// Load template from file
const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/noop_runs_issue.md`;
const templatePath = getPromptPath("noop_runs_issue.md");
const parentBodyContent = fs.readFileSync(templatePath, "utf8");

const parentBody = generateFooterWithExpiration({
Expand Down Expand Up @@ -184,7 +184,7 @@ async function main() {
}

// Load and render comment template from file
const commentTemplatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/noop_comment.md`;
const commentTemplatePath = getPromptPath("noop_comment.md");

// Compute effective tokens suffix from environment variable (set by parse_token_usage.cjs / parse_mcp_gateway_log.cjs)
const effectiveTokensSuffix = getEffectiveTokensSuffix();
Expand Down
17 changes: 17 additions & 0 deletions actions/setup/js/messages_core.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,22 @@ function renderTemplate(template, context) {
});
}

/**
* Resolve the absolute path to a prompt template file.
* Prefers GH_AW_PROMPTS_DIR when set, otherwise falls back to
* ${RUNNER_TEMP}/gh-aw/prompts (the runtime location used in production).
* Throws if neither GH_AW_PROMPTS_DIR nor RUNNER_TEMP is set.
* @param {string} name - Template filename (e.g. "agent_timeout.md")
* @returns {string} Absolute path to the prompt template file
*/
function getPromptPath(name) {
const promptsDir = process.env.GH_AW_PROMPTS_DIR || (process.env.RUNNER_TEMP ? `${process.env.RUNNER_TEMP}/gh-aw/prompts` : null);
if (!promptsDir) {
throw new Error("Cannot resolve prompt path: neither GH_AW_PROMPTS_DIR nor RUNNER_TEMP is set");
}
return `${promptsDir}/${name}`;
}
Comment on lines +95 to +101
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getPromptPath() can produce an invalid path when neither GH_AW_PROMPTS_DIR nor RUNNER_TEMP is set (e.g. "undefined/gh-aw/prompts/"). It also claims to return an absolute path but doesn’t enforce that. Consider validating that at least one base directory is configured (throwing a clear error), and build/normalize the path with path.join/path.resolve (also avoiding double slashes and platform separator issues).

Copilot uses AI. Check for mistakes.

/**
* Read a template file and render it with the given context.
* Combines file loading and template rendering into a single helper.
Expand Down Expand Up @@ -169,6 +185,7 @@ function buildProtectedFileList(files, githubServer, owner, repo, branch) {

module.exports = {
getMessages,
getPromptPath,
renderTemplate,
renderTemplateFromFile,
toSnakeCase,
Expand Down
Loading
Loading