diff --git a/.changeset/patch-standardize-safe-output-error-codes.md b/.changeset/patch-standardize-safe-output-error-codes.md
new file mode 100644
index 0000000000..56ea344ca8
--- /dev/null
+++ b/.changeset/patch-standardize-safe-output-error-codes.md
@@ -0,0 +1,5 @@
+---
+"gh-aw": patch
+---
+
+Added the new safe-output error code registry and updated every handler/test to use the standardized codes so messages are machine-readable and consistent.
diff --git a/actions/setup/js/add_comment.cjs b/actions/setup/js/add_comment.cjs
index 713898c85e..5b4b3d4eaf 100644
--- a/actions/setup/js/add_comment.cjs
+++ b/actions/setup/js/add_comment.cjs
@@ -17,6 +17,7 @@ const { getMessages } = require("./messages_core.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");
const { MAX_COMMENT_LENGTH, MAX_MENTIONS, MAX_LINKS, enforceCommentLimits } = require("./comment_limit_helpers.cjs");
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
+const { ERR_NOT_FOUND } = require("./error_codes.cjs");
/** @type {string} Safe output type handled by this module */
const HANDLER_TYPE = "add_comment";
@@ -229,7 +230,7 @@ async function commentOnDiscussion(github, owner, repo, discussionNumber, messag
);
if (!repository || !repository.discussion) {
- throw new Error(`Discussion #${discussionNumber} not found in ${owner}/${repo}`);
+ throw new Error(`${ERR_NOT_FOUND}: Discussion #${discussionNumber} not found in ${owner}/${repo}`);
}
const discussionId = repository.discussion.id;
@@ -522,7 +523,7 @@ async function main(config = {}) {
const discussionId = queryResult?.repository?.discussion?.id;
if (!discussionId) {
- throw new Error(`Discussion #${itemNumber} not found in ${itemRepo}`);
+ throw new Error(`${ERR_NOT_FOUND}: Discussion #${itemNumber} not found in ${itemRepo}`);
}
comment = await commentOnDiscussion(github, repoParts.owner, repoParts.repo, itemNumber, processedBody, null);
@@ -592,7 +593,7 @@ async function main(config = {}) {
const discussionId = queryResult?.repository?.discussion?.id;
if (!discussionId) {
- throw new Error(`Discussion #${itemNumber} not found in ${itemRepo}`);
+ throw new Error(`${ERR_NOT_FOUND}: Discussion #${itemNumber} not found in ${itemRepo}`);
}
core.info(`Found discussion #${itemNumber}, adding comment...`);
diff --git a/actions/setup/js/add_copilot_reviewer.cjs b/actions/setup/js/add_copilot_reviewer.cjs
index 6636214454..0bbf4e3767 100644
--- a/actions/setup/js/add_copilot_reviewer.cjs
+++ b/actions/setup/js/add_copilot_reviewer.cjs
@@ -2,6 +2,7 @@
///
const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_CONFIG, ERR_NOT_FOUND, ERR_VALIDATION } = require("./error_codes.cjs");
/**
* Add Copilot as a reviewer to a pull request.
@@ -22,13 +23,13 @@ async function main() {
const prNumberStr = process.env.PR_NUMBER?.trim();
if (!prNumberStr) {
- core.setFailed("PR_NUMBER environment variable is required but not set");
+ core.setFailed(`${ERR_CONFIG}: PR_NUMBER environment variable is required but not set`);
return;
}
const prNumber = parseInt(prNumberStr, 10);
if (isNaN(prNumber) || prNumber <= 0) {
- core.setFailed(`Invalid PR_NUMBER: ${prNumberStr}. Must be a positive integer.`);
+ core.setFailed(`${ERR_VALIDATION}: Invalid PR_NUMBER: ${prNumberStr}. Must be a positive integer.`);
return;
}
@@ -55,7 +56,7 @@ Successfully added Copilot as a reviewer to PR #${prNumber}.`
} catch (error) {
const errorMessage = getErrorMessage(error);
core.error(`Failed to add Copilot as reviewer: ${errorMessage}`);
- core.setFailed(`Failed to add Copilot as reviewer to PR #${prNumber}: ${errorMessage}`);
+ core.setFailed(`${ERR_NOT_FOUND}: Failed to add Copilot as reviewer to PR #${prNumber}: ${errorMessage}`);
}
}
diff --git a/actions/setup/js/add_copilot_reviewer.test.cjs b/actions/setup/js/add_copilot_reviewer.test.cjs
index 03ad132413..9e61365fd2 100644
--- a/actions/setup/js/add_copilot_reviewer.test.cjs
+++ b/actions/setup/js/add_copilot_reviewer.test.cjs
@@ -1,4 +1,5 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
+const { ERR_CONFIG } = require("./error_codes.cjs");
// Mock the global objects that GitHub Actions provides
const mockCore = {
@@ -76,7 +77,7 @@ describe("add_copilot_reviewer", () => {
await runScript();
- expect(mockCore.setFailed).toHaveBeenCalledWith("PR_NUMBER environment variable is required but not set");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_CONFIG}: PR_NUMBER environment variable is required but not set`);
expect(mockGithub.rest.pulls.requestReviewers).not.toHaveBeenCalled();
});
@@ -85,7 +86,7 @@ describe("add_copilot_reviewer", () => {
await runScript();
- expect(mockCore.setFailed).toHaveBeenCalledWith("PR_NUMBER environment variable is required but not set");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_CONFIG}: PR_NUMBER environment variable is required but not set`);
expect(mockGithub.rest.pulls.requestReviewers).not.toHaveBeenCalled();
});
diff --git a/actions/setup/js/add_reaction.cjs b/actions/setup/js/add_reaction.cjs
index f4de27252a..62e1ba7653 100644
--- a/actions/setup/js/add_reaction.cjs
+++ b/actions/setup/js/add_reaction.cjs
@@ -2,6 +2,7 @@
///
const { getErrorMessage, isLockedError } = require("./error_helpers.cjs");
+const { ERR_API, ERR_NOT_FOUND, ERR_VALIDATION } = require("./error_codes.cjs");
/**
* Add a reaction to the triggering item (issue, PR, comment, or discussion).
@@ -18,7 +19,7 @@ async function main() {
// Validate reaction type
const validReactions = ["+1", "-1", "laugh", "confused", "heart", "hooray", "rocket", "eyes"];
if (!validReactions.includes(reaction)) {
- core.setFailed(`Invalid reaction type: ${reaction}. Valid reactions are: ${validReactions.join(", ")}`);
+ core.setFailed(`${ERR_VALIDATION}: Invalid reaction type: ${reaction}. Valid reactions are: ${validReactions.join(", ")}`);
return;
}
@@ -33,7 +34,7 @@ async function main() {
case "issues":
const issueNumber = context.payload?.issue?.number;
if (!issueNumber) {
- core.setFailed("Issue number not found in event payload");
+ core.setFailed(`${ERR_NOT_FOUND}: Issue number not found in event payload`);
return;
}
reactionEndpoint = `/repos/${owner}/${repo}/issues/${issueNumber}/reactions`;
@@ -42,7 +43,7 @@ async function main() {
case "issue_comment":
const commentId = context.payload?.comment?.id;
if (!commentId) {
- core.setFailed("Comment ID not found in event payload");
+ core.setFailed(`${ERR_VALIDATION}: Comment ID not found in event payload`);
return;
}
reactionEndpoint = `/repos/${owner}/${repo}/issues/comments/${commentId}/reactions`;
@@ -51,7 +52,7 @@ async function main() {
case "pull_request":
const prNumber = context.payload?.pull_request?.number;
if (!prNumber) {
- core.setFailed("Pull request number not found in event payload");
+ core.setFailed(`${ERR_NOT_FOUND}: Pull request number not found in event payload`);
return;
}
// PRs are "issues" for the reactions endpoint
@@ -61,7 +62,7 @@ async function main() {
case "pull_request_review_comment":
const reviewCommentId = context.payload?.comment?.id;
if (!reviewCommentId) {
- core.setFailed("Review comment ID not found in event payload");
+ core.setFailed(`${ERR_VALIDATION}: Review comment ID not found in event payload`);
return;
}
reactionEndpoint = `/repos/${owner}/${repo}/pulls/comments/${reviewCommentId}/reactions`;
@@ -70,7 +71,7 @@ async function main() {
case "discussion":
const discussionNumber = context.payload?.discussion?.number;
if (!discussionNumber) {
- core.setFailed("Discussion number not found in event payload");
+ core.setFailed(`${ERR_NOT_FOUND}: Discussion number not found in event payload`);
return;
}
// Discussions use GraphQL API - get the node ID
@@ -81,14 +82,14 @@ async function main() {
case "discussion_comment":
const commentNodeId = context.payload?.comment?.node_id;
if (!commentNodeId) {
- core.setFailed("Discussion comment node ID not found in event payload");
+ core.setFailed(`${ERR_NOT_FOUND}: Discussion comment node ID not found in event payload`);
return;
}
await addDiscussionReaction(commentNodeId, reaction);
return; // Early return for discussion comment events
default:
- core.setFailed(`Unsupported event type: ${eventName}`);
+ core.setFailed(`${ERR_VALIDATION}: Unsupported event type: ${eventName}`);
return;
}
@@ -107,7 +108,7 @@ async function main() {
// For other errors, fail as before
core.error(`Failed to add reaction: ${errorMessage}`);
- core.setFailed(`Failed to add reaction: ${errorMessage}`);
+ core.setFailed(`${ERR_API}: Failed to add reaction: ${errorMessage}`);
}
}
@@ -154,7 +155,7 @@ async function addDiscussionReaction(subjectId, reaction) {
const reactionContent = reactionMap[reaction];
if (!reactionContent) {
- throw new Error(`Invalid reaction type for GraphQL: ${reaction}`);
+ throw new Error(`${ERR_VALIDATION}: Invalid reaction type for GraphQL: ${reaction}`);
}
const result = await github.graphql(
@@ -197,7 +198,7 @@ async function getDiscussionId(owner, repo, discussionNumber) {
);
if (!repository || !repository.discussion) {
- throw new Error(`Discussion #${discussionNumber} not found in ${owner}/${repo}`);
+ throw new Error(`${ERR_NOT_FOUND}: Discussion #${discussionNumber} not found in ${owner}/${repo}`);
}
return {
diff --git a/actions/setup/js/add_reaction.test.cjs b/actions/setup/js/add_reaction.test.cjs
index e5ff4ff9dc..6bc310bc76 100644
--- a/actions/setup/js/add_reaction.test.cjs
+++ b/actions/setup/js/add_reaction.test.cjs
@@ -1,5 +1,6 @@
// @ts-check
import { describe, it, expect, beforeEach, vi } from "vitest";
+const { ERR_NOT_FOUND, ERR_VALIDATION } = require("./error_codes.cjs");
// Mock the global objects that GitHub Actions provides
const mockCore = {
@@ -139,7 +140,7 @@ describe("add_reaction", () => {
await runScript();
- expect(mockCore.setFailed).toHaveBeenCalledWith("Issue number not found in event payload");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_NOT_FOUND}: Issue number not found in event payload`);
expect(mockGithub.request).not.toHaveBeenCalled();
});
});
@@ -166,7 +167,7 @@ describe("add_reaction", () => {
await runScript();
- expect(mockCore.setFailed).toHaveBeenCalledWith("Comment ID not found in event payload");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_VALIDATION}: Comment ID not found in event payload`);
});
});
@@ -192,7 +193,7 @@ describe("add_reaction", () => {
await runScript();
- expect(mockCore.setFailed).toHaveBeenCalledWith("Pull request number not found in event payload");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_NOT_FOUND}: Pull request number not found in event payload`);
});
});
@@ -218,7 +219,7 @@ describe("add_reaction", () => {
await runScript();
- expect(mockCore.setFailed).toHaveBeenCalledWith("Review comment ID not found in event payload");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_VALIDATION}: Review comment ID not found in event payload`);
});
});
@@ -268,7 +269,7 @@ describe("add_reaction", () => {
await runScript();
- expect(mockCore.setFailed).toHaveBeenCalledWith("Discussion number not found in event payload");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_NOT_FOUND}: Discussion number not found in event payload`);
});
it("should handle discussion not found error", async () => {
@@ -317,7 +318,7 @@ describe("add_reaction", () => {
await runScript();
- expect(mockCore.setFailed).toHaveBeenCalledWith("Discussion comment node ID not found in event payload");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_NOT_FOUND}: Discussion comment node ID not found in event payload`);
});
});
@@ -360,7 +361,7 @@ describe("add_reaction", () => {
await runScript();
- expect(mockCore.setFailed).toHaveBeenCalledWith("Unsupported event type: push");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_VALIDATION}: Unsupported event type: push`);
expect(mockGithub.request).not.toHaveBeenCalled();
});
});
diff --git a/actions/setup/js/add_reaction_and_edit_comment.cjs b/actions/setup/js/add_reaction_and_edit_comment.cjs
index 5dc2abde72..2f9810ab6b 100644
--- a/actions/setup/js/add_reaction_and_edit_comment.cjs
+++ b/actions/setup/js/add_reaction_and_edit_comment.cjs
@@ -5,6 +5,7 @@ const { getRunStartedMessage } = require("./messages_run_status.cjs");
const { getErrorMessage } = require("./error_helpers.cjs");
const { generateWorkflowIdMarker } = require("./generate_footer.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");
+const { ERR_API, ERR_NOT_FOUND, ERR_VALIDATION } = require("./error_codes.cjs");
async function main() {
// Read inputs from environment variables
@@ -22,7 +23,7 @@ async function main() {
// Validate reaction type
const validReactions = ["+1", "-1", "laugh", "confused", "heart", "hooray", "rocket", "eyes"];
if (!validReactions.includes(reaction)) {
- core.setFailed(`Invalid reaction type: ${reaction}. Valid reactions are: ${validReactions.join(", ")}`);
+ core.setFailed(`${ERR_VALIDATION}: Invalid reaction type: ${reaction}. Valid reactions are: ${validReactions.join(", ")}`);
return;
}
@@ -39,7 +40,7 @@ async function main() {
case "issues":
const issueNumber = context.payload?.issue?.number;
if (!issueNumber) {
- core.setFailed("Issue number not found in event payload");
+ core.setFailed(`${ERR_NOT_FOUND}: Issue number not found in event payload`);
return;
}
reactionEndpoint = `/repos/${owner}/${repo}/issues/${issueNumber}/reactions`;
@@ -52,11 +53,11 @@ async function main() {
const commentId = context.payload?.comment?.id;
const issueNumberForComment = context.payload?.issue?.number;
if (!commentId) {
- core.setFailed("Comment ID not found in event payload");
+ core.setFailed(`${ERR_VALIDATION}: Comment ID not found in event payload`);
return;
}
if (!issueNumberForComment) {
- core.setFailed("Issue number not found in event payload");
+ core.setFailed(`${ERR_NOT_FOUND}: Issue number not found in event payload`);
return;
}
reactionEndpoint = `/repos/${owner}/${repo}/issues/comments/${commentId}/reactions`;
@@ -69,7 +70,7 @@ async function main() {
case "pull_request":
const prNumber = context.payload?.pull_request?.number;
if (!prNumber) {
- core.setFailed("Pull request number not found in event payload");
+ core.setFailed(`${ERR_NOT_FOUND}: Pull request number not found in event payload`);
return;
}
// PRs are "issues" for the reactions endpoint
@@ -83,11 +84,11 @@ async function main() {
const reviewCommentId = context.payload?.comment?.id;
const prNumberForReviewComment = context.payload?.pull_request?.number;
if (!reviewCommentId) {
- core.setFailed("Review comment ID not found in event payload");
+ core.setFailed(`${ERR_VALIDATION}: Review comment ID not found in event payload`);
return;
}
if (!prNumberForReviewComment) {
- core.setFailed("Pull request number not found in event payload");
+ core.setFailed(`${ERR_NOT_FOUND}: Pull request number not found in event payload`);
return;
}
reactionEndpoint = `/repos/${owner}/${repo}/pulls/comments/${reviewCommentId}/reactions`;
@@ -100,7 +101,7 @@ async function main() {
case "discussion":
const discussionNumber = context.payload?.discussion?.number;
if (!discussionNumber) {
- core.setFailed("Discussion number not found in event payload");
+ core.setFailed(`${ERR_NOT_FOUND}: Discussion number not found in event payload`);
return;
}
// Discussions use GraphQL API - get the node ID
@@ -115,13 +116,13 @@ async function main() {
const discussionCommentNumber = context.payload?.discussion?.number;
const discussionCommentId = context.payload?.comment?.id;
if (!discussionCommentNumber || !discussionCommentId) {
- core.setFailed("Discussion or comment information not found in event payload");
+ core.setFailed(`${ERR_NOT_FOUND}: Discussion or comment information not found in event payload`);
return;
}
// Get the comment node ID from the payload
const commentNodeId = context.payload?.comment?.node_id;
if (!commentNodeId) {
- core.setFailed("Discussion comment node ID not found in event payload");
+ core.setFailed(`${ERR_NOT_FOUND}: Discussion comment node ID not found in event payload`);
return;
}
reactionEndpoint = commentNodeId; // Store node ID for GraphQL
@@ -131,7 +132,7 @@ async function main() {
break;
default:
- core.setFailed(`Unsupported event type: ${eventName}`);
+ core.setFailed(`${ERR_VALIDATION}: Unsupported event type: ${eventName}`);
return;
}
@@ -170,7 +171,7 @@ async function main() {
// For other errors, fail as before
core.error(`Failed to process reaction and comment creation: ${errorMessage}`);
- core.setFailed(`Failed to process reaction and comment creation: ${errorMessage}`);
+ core.setFailed(`${ERR_API}: Failed to process reaction and comment creation: ${errorMessage}`);
}
}
@@ -217,7 +218,7 @@ async function addDiscussionReaction(subjectId, reaction) {
const reactionContent = reactionMap[reaction];
if (!reactionContent) {
- throw new Error(`Invalid reaction type for GraphQL: ${reaction}`);
+ throw new Error(`${ERR_VALIDATION}: Invalid reaction type for GraphQL: ${reaction}`);
}
const result = await github.graphql(
@@ -260,7 +261,7 @@ async function getDiscussionId(owner, repo, discussionNumber) {
);
if (!repository || !repository.discussion) {
- throw new Error(`Discussion #${discussionNumber} not found in ${owner}/${repo}`);
+ throw new Error(`${ERR_NOT_FOUND}: Discussion #${discussionNumber} not found in ${owner}/${repo}`);
}
return {
@@ -280,7 +281,7 @@ async function getDiscussionId(owner, repo, discussionNumber) {
async function getDiscussionCommentId(owner, repo, discussionNumber, commentId) {
// First, get the discussion ID
const discussion = await getDiscussionId(owner, repo, discussionNumber);
- if (!discussion) throw new Error(`Discussion #${discussionNumber} not found in ${owner}/${repo}`);
+ if (!discussion) throw new Error(`${ERR_NOT_FOUND}: Discussion #${discussionNumber} not found in ${owner}/${repo}`);
// Then fetch the comment by traversing discussion comments
// Note: GitHub's GraphQL API doesn't provide a direct way to query comment by database ID
@@ -297,7 +298,7 @@ async function getDiscussionCommentId(owner, repo, discussionNumber, commentId)
};
}
- throw new Error(`Discussion comment node ID not found in event payload for comment ${commentId}`);
+ throw new Error(`${ERR_NOT_FOUND}: Discussion comment node ID not found in event payload for comment ${commentId}`);
}
/**
diff --git a/actions/setup/js/add_reaction_and_edit_comment.test.cjs b/actions/setup/js/add_reaction_and_edit_comment.test.cjs
index e204f2cb00..dc5c1a6c3b 100644
--- a/actions/setup/js/add_reaction_and_edit_comment.test.cjs
+++ b/actions/setup/js/add_reaction_and_edit_comment.test.cjs
@@ -1,6 +1,7 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
import fs from "fs";
import path from "path";
+const { ERR_NOT_FOUND, ERR_VALIDATION } = require("./error_codes.cjs");
const mockCore = {
debug: vi.fn(),
info: vi.fn(),
@@ -134,7 +135,7 @@ const mockCore = {
(global.context.eventName = "discussion_comment"),
(global.context.payload = { discussion: { number: 10 }, comment: { id: 123 }, repository: { html_url: "https://github.com/testowner/testrepo" } }),
await eval(`(async () => { ${reactionScript}; await main(); })()`),
- expect(mockCore.setFailed).toHaveBeenCalledWith("Discussion comment node ID not found in event payload"),
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_NOT_FOUND}: Discussion comment node ID not found in event payload`),
expect(mockGithub.graphql).not.toHaveBeenCalled());
}));
}),
@@ -220,21 +221,21 @@ const mockCore = {
(global.context.eventName = "discussion"),
(global.context.payload = { repository: { html_url: "https://github.com/testowner/testrepo" } }),
await eval(`(async () => { ${reactionScript}; await main(); })()`),
- expect(mockCore.setFailed).toHaveBeenCalledWith("Discussion number not found in event payload"));
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_NOT_FOUND}: Discussion number not found in event payload`));
}),
it("should handle missing discussion or comment info for discussion_comment", async () => {
((process.env.GH_AW_REACTION = "eyes"),
(global.context.eventName = "discussion_comment"),
(global.context.payload = { discussion: { number: 10 }, repository: { html_url: "https://github.com/testowner/testrepo" } }),
await eval(`(async () => { ${reactionScript}; await main(); })()`),
- expect(mockCore.setFailed).toHaveBeenCalledWith("Discussion or comment information not found in event payload"));
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_NOT_FOUND}: Discussion or comment information not found in event payload`));
}),
it("should handle unsupported event types", async () => {
((process.env.GH_AW_REACTION = "eyes"),
(global.context.eventName = "push"),
(global.context.payload = { repository: { html_url: "https://github.com/testowner/testrepo" } }),
await eval(`(async () => { ${reactionScript}; await main(); })()`),
- expect(mockCore.setFailed).toHaveBeenCalledWith("Unsupported event type: push"));
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_VALIDATION}: Unsupported event type: push`));
}),
it("should silently ignore locked issue errors (status 403)", async () => {
const lockedError = new Error("Issue is locked");
diff --git a/actions/setup/js/add_workflow_run_comment.cjs b/actions/setup/js/add_workflow_run_comment.cjs
index 98b22196d7..8cc5508ab2 100644
--- a/actions/setup/js/add_workflow_run_comment.cjs
+++ b/actions/setup/js/add_workflow_run_comment.cjs
@@ -5,6 +5,7 @@ const { getRunStartedMessage } = require("./messages_run_status.cjs");
const { getErrorMessage } = require("./error_helpers.cjs");
const { generateWorkflowIdMarker } = require("./generate_footer.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");
+const { ERR_NOT_FOUND, ERR_VALIDATION } = require("./error_codes.cjs");
/**
* Event type descriptions for comment messages
@@ -77,7 +78,7 @@ async function main() {
case "issues": {
const issueNumber = context.payload?.issue?.number;
if (!issueNumber) {
- core.setFailed("Issue number not found in event payload");
+ core.setFailed(`${ERR_NOT_FOUND}: Issue number not found in event payload`);
return;
}
commentEndpoint = `/repos/${owner}/${repo}/issues/${issueNumber}/comments`;
@@ -87,7 +88,7 @@ async function main() {
case "issue_comment": {
const issueNumberForComment = context.payload?.issue?.number;
if (!issueNumberForComment) {
- core.setFailed("Issue number not found in event payload");
+ core.setFailed(`${ERR_NOT_FOUND}: Issue number not found in event payload`);
return;
}
// Create new comment on the issue itself, not on the comment
@@ -98,7 +99,7 @@ async function main() {
case "pull_request": {
const prNumber = context.payload?.pull_request?.number;
if (!prNumber) {
- core.setFailed("Pull request number not found in event payload");
+ core.setFailed(`${ERR_NOT_FOUND}: Pull request number not found in event payload`);
return;
}
commentEndpoint = `/repos/${owner}/${repo}/issues/${prNumber}/comments`;
@@ -108,7 +109,7 @@ async function main() {
case "pull_request_review_comment": {
const prNumberForReviewComment = context.payload?.pull_request?.number;
if (!prNumberForReviewComment) {
- core.setFailed("Pull request number not found in event payload");
+ core.setFailed(`${ERR_NOT_FOUND}: Pull request number not found in event payload`);
return;
}
// Create new comment on the PR itself (using issues endpoint since PRs are issues)
@@ -119,7 +120,7 @@ async function main() {
case "discussion": {
const discussionNumber = context.payload?.discussion?.number;
if (!discussionNumber) {
- core.setFailed("Discussion number not found in event payload");
+ core.setFailed(`${ERR_NOT_FOUND}: Discussion number not found in event payload`);
return;
}
commentEndpoint = `discussion:${discussionNumber}`; // Special format to indicate discussion
@@ -130,7 +131,7 @@ async function main() {
const discussionCommentNumber = context.payload?.discussion?.number;
const discussionCommentId = context.payload?.comment?.id;
if (!discussionCommentNumber || !discussionCommentId) {
- core.setFailed("Discussion or comment information not found in event payload");
+ core.setFailed(`${ERR_NOT_FOUND}: Discussion or comment information not found in event payload`);
return;
}
commentEndpoint = `discussion_comment:${discussionCommentNumber}:${discussionCommentId}`; // Special format
@@ -138,7 +139,7 @@ async function main() {
}
default:
- core.setFailed(`Unsupported event type: ${eventName}`);
+ core.setFailed(`${ERR_VALIDATION}: Unsupported event type: ${eventName}`);
return;
}
diff --git a/actions/setup/js/add_workflow_run_comment.test.cjs b/actions/setup/js/add_workflow_run_comment.test.cjs
index 7df5a52883..02d265347e 100644
--- a/actions/setup/js/add_workflow_run_comment.test.cjs
+++ b/actions/setup/js/add_workflow_run_comment.test.cjs
@@ -1,5 +1,6 @@
// @ts-check
import { describe, it, expect, beforeEach, vi } from "vitest";
+const { ERR_NOT_FOUND, ERR_VALIDATION } = require("./error_codes.cjs");
// Mock the global objects that GitHub Actions provides
const mockCore = {
@@ -139,7 +140,7 @@ describe("add_workflow_run_comment", () => {
await runScript();
- expect(mockCore.setFailed).toHaveBeenCalledWith("Issue number not found in event payload");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_NOT_FOUND}: Issue number not found in event payload`);
expect(mockGithub.request).not.toHaveBeenCalled();
});
});
@@ -201,7 +202,7 @@ describe("add_workflow_run_comment", () => {
await runScript();
- expect(mockCore.setFailed).toHaveBeenCalledWith("Pull request number not found in event payload");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_NOT_FOUND}: Pull request number not found in event payload`);
expect(mockGithub.request).not.toHaveBeenCalled();
});
});
@@ -260,7 +261,7 @@ describe("add_workflow_run_comment", () => {
await runScript();
- expect(mockCore.setFailed).toHaveBeenCalledWith("Discussion number not found in event payload");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_NOT_FOUND}: Discussion number not found in event payload`);
});
});
@@ -303,7 +304,7 @@ describe("add_workflow_run_comment", () => {
await runScript();
- expect(mockCore.setFailed).toHaveBeenCalledWith("Discussion or comment information not found in event payload");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_NOT_FOUND}: Discussion or comment information not found in event payload`);
});
});
@@ -318,7 +319,7 @@ describe("add_workflow_run_comment", () => {
await runScript();
- expect(mockCore.setFailed).toHaveBeenCalledWith("Unsupported event type: unsupported_event");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_VALIDATION}: Unsupported event type: unsupported_event`);
expect(mockGithub.request).not.toHaveBeenCalled();
});
});
diff --git a/actions/setup/js/assign_copilot_to_created_issues.cjs b/actions/setup/js/assign_copilot_to_created_issues.cjs
index f52c38a29b..05a61c639b 100644
--- a/actions/setup/js/assign_copilot_to_created_issues.cjs
+++ b/actions/setup/js/assign_copilot_to_created_issues.cjs
@@ -4,6 +4,7 @@
const { AGENT_LOGIN_NAMES, findAgent, getIssueDetails, assignAgentToIssue, generatePermissionErrorSummary } = require("./assign_agent_helpers.cjs");
const { getErrorMessage } = require("./error_helpers.cjs");
const { sleep } = require("./error_recovery.cjs");
+const { ERR_API, ERR_PERMISSION } = require("./error_codes.cjs");
/**
* Assign copilot to issues created by create_issue job.
@@ -74,7 +75,7 @@ async function main() {
core.info(`Looking for ${agentName} coding agent...`);
agentId = await findAgent(owner, repo, agentName);
if (!agentId) {
- throw new Error(`${agentName} coding agent is not available for this repository`);
+ throw new Error(`${ERR_PERMISSION}: ${agentName} coding agent is not available for this repository`);
}
core.info(`Found ${agentName} coding agent (ID: ${agentId})`);
}
@@ -83,7 +84,7 @@ async function main() {
core.info(`Getting details for issue #${issueNumber} in ${repoSlug}...`);
const issueDetails = await getIssueDetails(owner, repo, issueNumber);
if (!issueDetails) {
- throw new Error("Failed to get issue details");
+ throw new Error(`${ERR_API}: Failed to get issue details`);
}
core.info(`Issue ID: ${issueDetails.issueId}`);
@@ -105,7 +106,7 @@ async function main() {
const success = await assignAgentToIssue(issueDetails.issueId, agentId, issueDetails.currentAssignees, agentName, null);
if (!success) {
- throw new Error(`Failed to assign ${agentName} via GraphQL`);
+ throw new Error(`${ERR_API}: Failed to assign ${agentName} via GraphQL`);
}
core.info(`Successfully assigned ${agentName} coding agent to issue #${issueNumber}`);
@@ -164,7 +165,7 @@ async function main() {
// Fail if any assignments failed
if (failureCount > 0) {
- core.setFailed(`Failed to assign copilot to ${failureCount} issue(s)`);
+ core.setFailed(`${ERR_API}: Failed to assign copilot to ${failureCount} issue(s)`);
}
}
diff --git a/actions/setup/js/assign_issue.cjs b/actions/setup/js/assign_issue.cjs
index 412ab586f6..5bbf202b1f 100644
--- a/actions/setup/js/assign_issue.cjs
+++ b/actions/setup/js/assign_issue.cjs
@@ -3,6 +3,7 @@
const { getAgentName, getIssueDetails, findAgent, assignAgentToIssue } = require("./assign_agent_helpers.cjs");
const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_API, ERR_CONFIG, ERR_NOT_FOUND, ERR_PERMISSION } = require("./error_codes.cjs");
/**
* Assign an issue to a user or bot (including copilot)
@@ -18,19 +19,19 @@ async function main() {
// Check if GH_TOKEN is present
if (!ghToken?.trim()) {
const docsUrl = "https://github.github.com/gh-aw/reference/safe-outputs/#assigning-issues-to-copilot";
- core.setFailed(`GH_TOKEN environment variable is required but not set. This token is needed to assign issues. For more information on configuring Copilot tokens, see: ${docsUrl}`);
+ core.setFailed(`${ERR_CONFIG}: GH_TOKEN environment variable is required but not set. This token is needed to assign issues. For more information on configuring Copilot tokens, see: ${docsUrl}`);
return;
}
// Validate assignee
if (!assignee?.trim()) {
- core.setFailed("ASSIGNEE environment variable is required but not set");
+ core.setFailed(`${ERR_CONFIG}: ASSIGNEE environment variable is required but not set`);
return;
}
// Validate issue number
if (!issueNumber?.trim()) {
- core.setFailed("ISSUE_NUMBER environment variable is required but not set");
+ core.setFailed(`${ERR_CONFIG}: ISSUE_NUMBER environment variable is required but not set`);
return;
}
@@ -54,14 +55,14 @@ async function main() {
// Find the agent in the repository
const agentId = await findAgent(owner, repo, agentName);
if (!agentId) {
- throw new Error(`${agentName} coding agent is not available for this repository`);
+ throw new Error(`${ERR_PERMISSION}: ${agentName} coding agent is not available for this repository`);
}
core.info(`Found ${agentName} coding agent (ID: ${agentId})`);
// Get issue details
const issueDetails = await getIssueDetails(owner, repo, issueNum);
if (!issueDetails) {
- throw new Error("Failed to get issue details");
+ throw new Error(`${ERR_API}: Failed to get issue details`);
}
// Check if agent is already assigned
@@ -72,7 +73,7 @@ async function main() {
const success = await assignAgentToIssue(issueDetails.issueId, agentId, issueDetails.currentAssignees, agentName, null);
if (!success) {
- throw new Error(`Failed to assign ${agentName} via GraphQL`);
+ throw new Error(`${ERR_API}: Failed to assign ${agentName} via GraphQL`);
}
}
} else {
@@ -89,7 +90,7 @@ async function main() {
} catch (error) {
const errorMessage = getErrorMessage(error);
core.error(`Failed to assign issue: ${errorMessage}`);
- core.setFailed(`Failed to assign issue #${issueNum} to ${trimmedAssignee}: ${errorMessage}`);
+ core.setFailed(`${ERR_NOT_FOUND}: Failed to assign issue #${issueNum} to ${trimmedAssignee}: ${errorMessage}`);
}
}
diff --git a/actions/setup/js/assign_issue.test.cjs b/actions/setup/js/assign_issue.test.cjs
index 159816cd53..44fd507e43 100644
--- a/actions/setup/js/assign_issue.test.cjs
+++ b/actions/setup/js/assign_issue.test.cjs
@@ -1,6 +1,7 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
import fs from "fs";
import path from "path";
+const { ERR_CONFIG, ERR_NOT_FOUND } = require("./error_codes.cjs");
const mockCore = {
debug: vi.fn(),
info: vi.fn(),
@@ -65,7 +66,7 @@ const mockCore = {
(process.env.ISSUE_NUMBER = "123"),
delete process.env.ASSIGNEE,
await eval(`(async () => { ${assignIssueScript}; await main(); })()`),
- expect(mockCore.setFailed).toHaveBeenCalledWith("ASSIGNEE environment variable is required but not set"),
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_CONFIG}: ASSIGNEE environment variable is required but not set`),
expect(mockExec.exec).not.toHaveBeenCalled());
}),
it("should fail when ASSIGNEE is empty string", async () => {
@@ -73,7 +74,7 @@ const mockCore = {
(process.env.ASSIGNEE = " "),
(process.env.ISSUE_NUMBER = "123"),
await eval(`(async () => { ${assignIssueScript}; await main(); })()`),
- expect(mockCore.setFailed).toHaveBeenCalledWith("ASSIGNEE environment variable is required but not set"),
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_CONFIG}: ASSIGNEE environment variable is required but not set`),
expect(mockExec.exec).not.toHaveBeenCalled());
}),
it("should fail when ISSUE_NUMBER is not set", async () => {
@@ -81,7 +82,7 @@ const mockCore = {
(process.env.ASSIGNEE = "test-user"),
delete process.env.ISSUE_NUMBER,
await eval(`(async () => { ${assignIssueScript}; await main(); })()`),
- expect(mockCore.setFailed).toHaveBeenCalledWith("ISSUE_NUMBER environment variable is required but not set"),
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_CONFIG}: ISSUE_NUMBER environment variable is required but not set`),
expect(mockExec.exec).not.toHaveBeenCalled());
}),
it("should fail when ISSUE_NUMBER is empty string", async () => {
@@ -89,7 +90,7 @@ const mockCore = {
(process.env.ASSIGNEE = "test-user"),
(process.env.ISSUE_NUMBER = " "),
await eval(`(async () => { ${assignIssueScript}; await main(); })()`),
- expect(mockCore.setFailed).toHaveBeenCalledWith("ISSUE_NUMBER environment variable is required but not set"),
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_CONFIG}: ISSUE_NUMBER environment variable is required but not set`),
expect(mockExec.exec).not.toHaveBeenCalled());
}));
}),
@@ -135,7 +136,7 @@ const mockCore = {
(mockExec.exec.mockRejectedValue(testError),
await eval(`(async () => { ${assignIssueScript}; await main(); })()`),
expect(mockCore.error).toHaveBeenCalledWith("Failed to assign issue: User not found"),
- expect(mockCore.setFailed).toHaveBeenCalledWith("Failed to assign issue #999 to test-user: User not found"));
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_NOT_FOUND}: Failed to assign issue #999 to test-user: User not found`));
}),
it("should handle non-Error objects in catch block", async () => {
((process.env.GH_TOKEN = "ghp_test123"), (process.env.ASSIGNEE = "test-user"), (process.env.ISSUE_NUMBER = "999"));
@@ -143,7 +144,7 @@ const mockCore = {
(mockExec.exec.mockRejectedValue(stringError),
await eval(`(async () => { ${assignIssueScript}; await main(); })()`),
expect(mockCore.error).toHaveBeenCalledWith("Failed to assign issue: Command failed"),
- expect(mockCore.setFailed).toHaveBeenCalledWith("Failed to assign issue #999 to test-user: Command failed"));
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_NOT_FOUND}: Failed to assign issue #999 to test-user: Command failed`));
}),
it("should handle top-level errors with catch handler", async () => {
((process.env.GH_TOKEN = "ghp_test123"), (process.env.ASSIGNEE = "test-user"), (process.env.ISSUE_NUMBER = "123"));
diff --git a/actions/setup/js/check_command_position.cjs b/actions/setup/js/check_command_position.cjs
index 90787871aa..9feabed4db 100644
--- a/actions/setup/js/check_command_position.cjs
+++ b/actions/setup/js/check_command_position.cjs
@@ -1,6 +1,8 @@
// @ts-check
///
+const { ERR_API, ERR_CONFIG, ERR_VALIDATION } = require("./error_codes.cjs");
+
/**
* Check if command is the first word in the triggering text
* This prevents accidental command triggers from words appearing later in content
@@ -12,7 +14,7 @@ async function main() {
const { getErrorMessage } = require("./error_helpers.cjs");
if (!commandsJSON) {
- core.setFailed("Configuration error: GH_AW_COMMANDS not specified.");
+ core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_COMMANDS not specified.`);
return;
}
@@ -21,16 +23,16 @@ async function main() {
try {
commands = JSON.parse(commandsJSON);
if (!Array.isArray(commands)) {
- core.setFailed("Configuration error: GH_AW_COMMANDS must be an array.");
+ core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_COMMANDS must be an array.`);
return;
}
} catch (error) {
- core.setFailed(`Configuration error: Failed to parse GH_AW_COMMANDS: ${getErrorMessage(error)}`);
+ core.setFailed(`${ERR_CONFIG}: Configuration error: Failed to parse GH_AW_COMMANDS: ${getErrorMessage(error)}`);
return;
}
if (commands.length === 0) {
- core.setFailed("Configuration error: No commands specified.");
+ core.setFailed(`${ERR_CONFIG}: Configuration error: No commands specified.`);
return;
}
@@ -88,7 +90,7 @@ async function main() {
core.setOutput("matched_command", "");
}
} catch (error) {
- core.setFailed(getErrorMessage(error));
+ core.setFailed(`${ERR_API}: ${getErrorMessage(error)}`);
}
}
diff --git a/actions/setup/js/check_command_position.test.cjs b/actions/setup/js/check_command_position.test.cjs
index 5c3d10fdce..636c1c12da 100644
--- a/actions/setup/js/check_command_position.test.cjs
+++ b/actions/setup/js/check_command_position.test.cjs
@@ -1,6 +1,7 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import fs from "fs";
import path from "path";
+const { ERR_CONFIG } = require("./error_codes.cjs");
const mockCore = {
debug: vi.fn(),
info: vi.fn(),
@@ -42,7 +43,9 @@ const mockCore = {
void 0 !== originalEnv.GH_AW_COMMANDS ? (process.env.GH_AW_COMMANDS = originalEnv.GH_AW_COMMANDS) : delete process.env.GH_AW_COMMANDS;
}),
it("should fail when GH_AW_COMMANDS is not set", async () => {
- (delete process.env.GH_AW_COMMANDS, await eval(`(async () => { ${checkCommandPositionScript}; await main(); })()`), expect(mockCore.setFailed).toHaveBeenCalledWith("Configuration error: GH_AW_COMMANDS not specified."));
+ (delete process.env.GH_AW_COMMANDS,
+ await eval(`(async () => { ${checkCommandPositionScript}; await main(); })()`),
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_CONFIG}: Configuration error: GH_AW_COMMANDS not specified.`));
}),
it("should pass when command is the first word in issue body", async () => {
((process.env.GH_AW_COMMANDS = JSON.stringify(["test-bot"])),
diff --git a/actions/setup/js/check_permissions.cjs b/actions/setup/js/check_permissions.cjs
index 5ba831db3f..da433fae8c 100644
--- a/actions/setup/js/check_permissions.cjs
+++ b/actions/setup/js/check_permissions.cjs
@@ -2,6 +2,7 @@
///
const { parseRequiredPermissions, checkRepositoryPermission } = require("./check_permissions_utils.cjs");
+const { ERR_API, ERR_CONFIG, ERR_PERMISSION } = require("./error_codes.cjs");
async function main() {
const { eventName, actor, repo } = context;
@@ -26,7 +27,7 @@ async function main() {
if (!requiredPermissions || requiredPermissions.length === 0) {
core.error("❌ Configuration error: Required permissions not specified. Contact repository administrator.");
- core.setFailed("Configuration error: Required permissions not specified");
+ core.setFailed(`${ERR_CONFIG}: Configuration error: Required permissions not specified`);
return;
}
@@ -34,14 +35,14 @@ async function main() {
const result = await checkRepositoryPermission(actor, owner, repoName, requiredPermissions);
if (result.error) {
- core.setFailed(`Repository permission check failed: ${result.error}`);
+ core.setFailed(`${ERR_API}: Repository permission check failed: ${result.error}`);
return;
}
if (!result.authorized) {
// Fail the workflow when permission check fails (cancellation handled by activation job's if condition)
core.warning(`Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`);
- core.setFailed(`Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`);
+ core.setFailed(`${ERR_PERMISSION}: Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`);
}
}
diff --git a/actions/setup/js/check_skip_if_match.cjs b/actions/setup/js/check_skip_if_match.cjs
index 95093621a6..d646d03c93 100644
--- a/actions/setup/js/check_skip_if_match.cjs
+++ b/actions/setup/js/check_skip_if_match.cjs
@@ -2,6 +2,7 @@
///
const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_API, ERR_CONFIG } = require("./error_codes.cjs");
async function main() {
const skipQuery = process.env.GH_AW_SKIP_QUERY;
@@ -9,18 +10,18 @@ async function main() {
const maxMatchesStr = process.env.GH_AW_SKIP_MAX_MATCHES ?? "1";
if (!skipQuery) {
- core.setFailed("Configuration error: GH_AW_SKIP_QUERY not specified.");
+ core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_SKIP_QUERY not specified.`);
return;
}
if (!workflowName) {
- core.setFailed("Configuration error: GH_AW_WORKFLOW_NAME not specified.");
+ core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_WORKFLOW_NAME not specified.`);
return;
}
const maxMatches = parseInt(maxMatchesStr, 10);
if (isNaN(maxMatches) || maxMatches < 1) {
- core.setFailed(`Configuration error: GH_AW_SKIP_MAX_MATCHES must be a positive integer, got "${maxMatchesStr}".`);
+ core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_SKIP_MAX_MATCHES must be a positive integer, got "${maxMatchesStr}".`);
return;
}
@@ -50,7 +51,7 @@ async function main() {
core.info(`✓ Found ${totalCount} matches (below threshold of ${maxMatches}), workflow can proceed`);
core.setOutput("skip_check_ok", "true");
} catch (error) {
- core.setFailed(`Failed to execute search query: ${getErrorMessage(error)}`);
+ core.setFailed(`${ERR_API}: Failed to execute search query: ${getErrorMessage(error)}`);
}
}
diff --git a/actions/setup/js/check_skip_if_no_match.cjs b/actions/setup/js/check_skip_if_no_match.cjs
index 8198affe66..083ea68eb1 100644
--- a/actions/setup/js/check_skip_if_no_match.cjs
+++ b/actions/setup/js/check_skip_if_no_match.cjs
@@ -2,23 +2,24 @@
///
const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_API, ERR_CONFIG } = require("./error_codes.cjs");
async function main() {
const { GH_AW_SKIP_QUERY: skipQuery, GH_AW_WORKFLOW_NAME: workflowName, GH_AW_SKIP_MIN_MATCHES: minMatchesStr = "1" } = process.env;
if (!skipQuery) {
- core.setFailed("Configuration error: GH_AW_SKIP_QUERY not specified.");
+ core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_SKIP_QUERY not specified.`);
return;
}
if (!workflowName) {
- core.setFailed("Configuration error: GH_AW_WORKFLOW_NAME not specified.");
+ core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_WORKFLOW_NAME not specified.`);
return;
}
const minMatches = parseInt(minMatchesStr, 10);
if (isNaN(minMatches) || minMatches < 1) {
- core.setFailed(`Configuration error: GH_AW_SKIP_MIN_MATCHES must be a positive integer, got "${minMatchesStr}".`);
+ core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_SKIP_MIN_MATCHES must be a positive integer, got "${minMatchesStr}".`);
return;
}
@@ -49,7 +50,7 @@ async function main() {
core.info(`✓ Found ${totalCount} matches (meets or exceeds minimum of ${minMatches}), workflow can proceed`);
core.setOutput("skip_no_match_check_ok", "true");
} catch (error) {
- core.setFailed(`Failed to execute search query: ${getErrorMessage(error)}`);
+ core.setFailed(`${ERR_API}: Failed to execute search query: ${getErrorMessage(error)}`);
}
}
diff --git a/actions/setup/js/check_skip_if_no_match.test.cjs b/actions/setup/js/check_skip_if_no_match.test.cjs
index 3959ecd618..449010078a 100644
--- a/actions/setup/js/check_skip_if_no_match.test.cjs
+++ b/actions/setup/js/check_skip_if_no_match.test.cjs
@@ -1,6 +1,7 @@
// @ts-check
import { describe, it, expect, beforeEach } from "vitest";
const { main } = require("./check_skip_if_no_match.cjs");
+const { ERR_API, ERR_CONFIG } = require("./error_codes.cjs");
describe("check_skip_if_no_match", () => {
let mockCore;
@@ -62,7 +63,7 @@ describe("check_skip_if_no_match", () => {
await main();
- expect(mockCore.errors).toContain("Configuration error: GH_AW_SKIP_QUERY not specified.");
+ expect(mockCore.errors).toContain(`${ERR_CONFIG}: Configuration error: GH_AW_SKIP_QUERY not specified.`);
});
it("should fail when GH_AW_WORKFLOW_NAME is not specified", async () => {
@@ -71,7 +72,7 @@ describe("check_skip_if_no_match", () => {
await main();
- expect(mockCore.errors).toContain("Configuration error: GH_AW_WORKFLOW_NAME not specified.");
+ expect(mockCore.errors).toContain(`${ERR_CONFIG}: Configuration error: GH_AW_WORKFLOW_NAME not specified.`);
});
it("should fail when GH_AW_SKIP_MIN_MATCHES is not a valid positive integer", async () => {
@@ -81,7 +82,7 @@ describe("check_skip_if_no_match", () => {
await main();
- expect(mockCore.errors).toContain('Configuration error: GH_AW_SKIP_MIN_MATCHES must be a positive integer, got "invalid".');
+ expect(mockCore.errors).toContain(`${ERR_CONFIG}: Configuration error: GH_AW_SKIP_MIN_MATCHES must be a positive integer, got "invalid".`);
});
it("should fail when GH_AW_SKIP_MIN_MATCHES is zero", async () => {
@@ -91,7 +92,7 @@ describe("check_skip_if_no_match", () => {
await main();
- expect(mockCore.errors).toContain('Configuration error: GH_AW_SKIP_MIN_MATCHES must be a positive integer, got "0".');
+ expect(mockCore.errors).toContain(`${ERR_CONFIG}: Configuration error: GH_AW_SKIP_MIN_MATCHES must be a positive integer, got "0".`);
});
it("should use default min matches of 1 when not specified", async () => {
@@ -195,7 +196,7 @@ describe("check_skip_if_no_match", () => {
await main();
- expect(mockCore.errors).toContain("Failed to execute search query: API rate limit exceeded");
+ expect(mockCore.errors).toContain(`${ERR_API}: Failed to execute search query: API rate limit exceeded`);
});
it("should log info messages during execution", async () => {
diff --git a/actions/setup/js/check_stop_time.cjs b/actions/setup/js/check_stop_time.cjs
index 4e86da0f8a..6addb4c216 100644
--- a/actions/setup/js/check_stop_time.cjs
+++ b/actions/setup/js/check_stop_time.cjs
@@ -1,17 +1,18 @@
// @ts-check
///
+const { ERR_CONFIG, ERR_VALIDATION } = require("./error_codes.cjs");
async function main() {
const stopTime = process.env.GH_AW_STOP_TIME;
const workflowName = process.env.GH_AW_WORKFLOW_NAME;
if (!stopTime) {
- core.setFailed("Configuration error: GH_AW_STOP_TIME not specified.");
+ core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_STOP_TIME not specified.`);
return;
}
if (!workflowName) {
- core.setFailed("Configuration error: GH_AW_WORKFLOW_NAME not specified.");
+ core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_WORKFLOW_NAME not specified.`);
return;
}
@@ -21,7 +22,7 @@ async function main() {
const stopTimeDate = new Date(stopTime);
if (isNaN(stopTimeDate.getTime())) {
- core.setFailed(`Invalid stop-time format: ${stopTime}. Expected format: YYYY-MM-DD HH:MM:SS`);
+ core.setFailed(`${ERR_VALIDATION}: Invalid stop-time format: ${stopTime}. Expected format: YYYY-MM-DD HH:MM:SS`);
return;
}
diff --git a/actions/setup/js/check_team_member.cjs b/actions/setup/js/check_team_member.cjs
index e9fe8d138d..5d24eeb4fc 100644
--- a/actions/setup/js/check_team_member.cjs
+++ b/actions/setup/js/check_team_member.cjs
@@ -5,6 +5,7 @@
* Check if user has admin or maintainer permissions
* @returns {Promise}
*/
+const { ERR_PERMISSION } = require("./error_codes.cjs");
async function main() {
const actor = context.actor;
const { owner, repo } = context.repo;
@@ -34,7 +35,7 @@ async function main() {
// Fail the workflow when team membership check fails (cancellation handled by activation job's if condition)
core.warning(`Access denied: Only authorized team members can trigger this workflow. User '${actor}' is not authorized.`);
- core.setFailed(`Access denied: User '${actor}' is not authorized for this workflow`);
+ core.setFailed(`${ERR_PERMISSION}: Access denied: User '${actor}' is not authorized for this workflow`);
core.setOutput("is_team_member", "false");
}
diff --git a/actions/setup/js/check_workflow_timestamp.cjs b/actions/setup/js/check_workflow_timestamp.cjs
index 5fd61d1587..faab4288b0 100644
--- a/actions/setup/js/check_workflow_timestamp.cjs
+++ b/actions/setup/js/check_workflow_timestamp.cjs
@@ -9,18 +9,19 @@
const fs = require("fs");
const path = require("path");
+const { ERR_CONFIG } = require("./error_codes.cjs");
async function main() {
const workspace = process.env.GITHUB_WORKSPACE;
const workflowFile = process.env.GH_AW_WORKFLOW_FILE;
if (!workspace) {
- core.setFailed("Configuration error: GITHUB_WORKSPACE not available.");
+ core.setFailed(`${ERR_CONFIG}: Configuration error: GITHUB_WORKSPACE not available.`);
return;
}
if (!workflowFile) {
- core.setFailed("Configuration error: GH_AW_WORKFLOW_FILE not available.");
+ core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_WORKFLOW_FILE not available.`);
return;
}
diff --git a/actions/setup/js/check_workflow_timestamp_api.cjs b/actions/setup/js/check_workflow_timestamp_api.cjs
index a3cd2d3f27..f77d5cd46f 100644
--- a/actions/setup/js/check_workflow_timestamp_api.cjs
+++ b/actions/setup/js/check_workflow_timestamp_api.cjs
@@ -10,12 +10,13 @@
const { getErrorMessage } = require("./error_helpers.cjs");
const { extractHashFromLockFile, computeFrontmatterHash, createGitHubFileReader } = require("./frontmatter_hash_pure.cjs");
const { getFileContent } = require("./github_api_helpers.cjs");
+const { ERR_CONFIG, ERR_VALIDATION } = require("./error_codes.cjs");
async function main() {
const workflowFile = process.env.GH_AW_WORKFLOW_FILE;
if (!workflowFile) {
- core.setFailed("Configuration error: GH_AW_WORKFLOW_FILE not available.");
+ core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_WORKFLOW_FILE not available.`);
return;
}
@@ -159,7 +160,7 @@ async function main() {
await summary.write();
// Fail the step to prevent workflow from running with outdated configuration
- core.setFailed(warningMessage);
+ core.setFailed(`${ERR_CONFIG}: ${warningMessage}`);
} else if (hashComparison.match) {
// Hashes match - lock file is up to date despite timestamp difference
core.info("✅ Lock file is up to date (frontmatter hashes match despite timestamp difference)");
@@ -189,7 +190,7 @@ async function main() {
await summary.write();
// Fail the step to prevent workflow from running with outdated configuration
- core.setFailed(warningMessage);
+ core.setFailed(`${ERR_CONFIG}: ${warningMessage}`);
}
} else if (workflowCommit.sha === lockCommit.sha) {
// Same commit - definitely up to date
@@ -233,7 +234,7 @@ async function main() {
await summary.write();
// Fail the step to prevent workflow from running with outdated configuration
- core.setFailed(warningMessage);
+ core.setFailed(`${ERR_CONFIG}: ${warningMessage}`);
}
} else {
// Lock file is newer than workflow file
diff --git a/actions/setup/js/checkout_pr_branch.cjs b/actions/setup/js/checkout_pr_branch.cjs
index 4fad71ceac..e48e687906 100644
--- a/actions/setup/js/checkout_pr_branch.cjs
+++ b/actions/setup/js/checkout_pr_branch.cjs
@@ -29,6 +29,7 @@ const { getErrorMessage } = require("./error_helpers.cjs");
const { renderTemplate } = require("./messages_core.cjs");
const { detectForkPR } = require("./pr_helpers.cjs");
const fs = require("fs");
+const { ERR_API } = require("./error_codes.cjs");
/**
* Log detailed PR context information for debugging
@@ -231,7 +232,7 @@ Pull request #${pullRequest.number} is closed. The checkout failed because the b
});
await core.summary.addRaw(summaryContent).write();
- core.setFailed(`Failed to checkout PR branch: ${errorMsg}`);
+ core.setFailed(`${ERR_API}: Failed to checkout PR branch: ${errorMsg}`);
}
}
diff --git a/actions/setup/js/checkout_pr_branch.test.cjs b/actions/setup/js/checkout_pr_branch.test.cjs
index 6466264573..bdaa893645 100644
--- a/actions/setup/js/checkout_pr_branch.test.cjs
+++ b/actions/setup/js/checkout_pr_branch.test.cjs
@@ -1,5 +1,5 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
-
+const { ERR_API } = require("./error_codes.cjs");
describe("checkout_pr_branch.cjs", () => {
let mockCore;
let mockExec;
@@ -153,6 +153,9 @@ If the pull request is still open, verify that:
},
};
}
+ if (module === "./error_codes.cjs") {
+ return require("./error_codes.cjs");
+ }
throw new Error(`Module ${module} not mocked in test`);
};
@@ -206,7 +209,7 @@ If the pull request is still open, verify that:
expect(summaryCall).toContain("git fetch failed");
expect(summaryCall).toContain("pull request has been closed");
- expect(mockCore.setFailed).toHaveBeenCalledWith("Failed to checkout PR branch: git fetch failed");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_API}: Failed to checkout PR branch: git fetch failed`);
});
it("should handle git checkout errors", async () => {
@@ -222,7 +225,7 @@ If the pull request is still open, verify that:
expect(summaryCall).toContain("Failed to Checkout PR Branch");
expect(summaryCall).toContain("git checkout failed");
- expect(mockCore.setFailed).toHaveBeenCalledWith("Failed to checkout PR branch: git checkout failed");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_API}: Failed to checkout PR branch: git checkout failed`);
});
});
@@ -266,7 +269,7 @@ If the pull request is still open, verify that:
expect(summaryCall).toContain("gh pr checkout failed");
expect(summaryCall).toContain("pull request has been closed");
- expect(mockCore.setFailed).toHaveBeenCalledWith("Failed to checkout PR branch: gh pr checkout failed");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_API}: Failed to checkout PR branch: gh pr checkout failed`);
});
it("should pass environment variables to gh command", async () => {
@@ -355,7 +358,7 @@ If the pull request is still open, verify that:
await runScript();
- expect(mockCore.setFailed).toHaveBeenCalledWith("Failed to checkout PR branch: string error");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_API}: Failed to checkout PR branch: string error`);
});
it("should handle errors with custom messages", async () => {
@@ -364,7 +367,7 @@ If the pull request is still open, verify that:
await runScript();
- expect(mockCore.setFailed).toHaveBeenCalledWith("Failed to checkout PR branch: Permission denied: unable to access repository");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_API}: Failed to checkout PR branch: Permission denied: unable to access repository`);
});
});
@@ -420,7 +423,7 @@ If the pull request is still open, verify that:
await runScript();
expect(mockCore.setOutput).toHaveBeenCalledWith("checkout_pr_success", "false");
- expect(mockCore.setFailed).toHaveBeenCalledWith("Failed to checkout PR branch: checkout failed");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_API}: Failed to checkout PR branch: checkout failed`);
});
it("should set output to true when no PR context", async () => {
@@ -630,7 +633,7 @@ If the pull request is still open, verify that:
expect(mockCore.error).toHaveBeenCalledWith("Event type: pull_request");
// Should fail the step
- expect(mockCore.setFailed).toHaveBeenCalledWith("Failed to checkout PR branch: network error");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_API}: Failed to checkout PR branch: network error`);
expect(mockCore.setOutput).toHaveBeenCalledWith("checkout_pr_success", "false");
});
diff --git a/actions/setup/js/close_discussion.cjs b/actions/setup/js/close_discussion.cjs
index 10d580947e..4e929453ea 100644
--- a/actions/setup/js/close_discussion.cjs
+++ b/actions/setup/js/close_discussion.cjs
@@ -8,6 +8,7 @@
const { getErrorMessage } = require("./error_helpers.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
+const { ERR_NOT_FOUND } = require("./error_codes.cjs");
/**
* Get discussion details using GraphQL with pagination for labels
@@ -53,7 +54,7 @@ async function getDiscussionDetails(github, owner, repo, discussionNumber) {
);
if (!query?.repository?.discussion) {
- throw new Error(`Discussion #${discussionNumber} not found in ${owner}/${repo}`);
+ throw new Error(`${ERR_NOT_FOUND}: Discussion #${discussionNumber} not found in ${owner}/${repo}`);
}
// Store the discussion metadata from the first query
@@ -75,7 +76,7 @@ async function getDiscussionDetails(github, owner, repo, discussionNumber) {
}
if (!discussion) {
- throw new Error(`Failed to fetch discussion #${discussionNumber}`);
+ throw new Error(`${ERR_NOT_FOUND}: Failed to fetch discussion #${discussionNumber}`);
}
return {
diff --git a/actions/setup/js/close_issue.cjs b/actions/setup/js/close_issue.cjs
index 02d5deff95..47ca716ab2 100644
--- a/actions/setup/js/close_issue.cjs
+++ b/actions/setup/js/close_issue.cjs
@@ -9,6 +9,7 @@ const { getErrorMessage } = require("./error_helpers.cjs");
const { resolveTargetRepoConfig, resolveAndValidateRepo } = require("./repo_helpers.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
+const { ERR_NOT_FOUND } = require("./error_codes.cjs");
/**
* Get issue details using REST API
@@ -26,7 +27,7 @@ async function getIssueDetails(github, owner, repo, issueNumber) {
});
if (!issue) {
- throw new Error(`Issue #${issueNumber} not found in ${owner}/${repo}`);
+ throw new Error(`${ERR_NOT_FOUND}: Issue #${issueNumber} not found in ${owner}/${repo}`);
}
return issue;
diff --git a/actions/setup/js/close_pull_request.cjs b/actions/setup/js/close_pull_request.cjs
index 5712039bd2..4a64ba512c 100644
--- a/actions/setup/js/close_pull_request.cjs
+++ b/actions/setup/js/close_pull_request.cjs
@@ -6,6 +6,7 @@ const { getTrackerID } = require("./get_tracker_id.cjs");
const { generateFooterWithMessages } = require("./messages_footer.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
+const { ERR_NOT_FOUND } = require("./error_codes.cjs");
/**
* @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction
@@ -29,7 +30,7 @@ async function getPullRequestDetails(github, owner, repo, prNumber) {
});
if (!pr) {
- throw new Error(`Pull request #${prNumber} not found in ${owner}/${repo}`);
+ throw new Error(`${ERR_NOT_FOUND}: Pull request #${prNumber} not found in ${owner}/${repo}`);
}
return pr;
diff --git a/actions/setup/js/collect_ndjson_output.cjs b/actions/setup/js/collect_ndjson_output.cjs
index c7410d351b..2e84d5aa00 100644
--- a/actions/setup/js/collect_ndjson_output.cjs
+++ b/actions/setup/js/collect_ndjson_output.cjs
@@ -4,6 +4,7 @@
const { getErrorMessage } = require("./error_helpers.cjs");
const { repairJson, sanitizePrototypePollution } = require("./json_repair_helpers.cjs");
const { AGENT_OUTPUT_FILENAME, TMP_GH_AW_PATH } = require("./constants.cjs");
+const { ERR_API, ERR_PARSE } = require("./error_codes.cjs");
async function main() {
try {
@@ -141,7 +142,7 @@ async function main() {
core.info(`invalid input json: ${jsonStr}`);
const originalMsg = originalError instanceof Error ? originalError.message : String(originalError);
const repairMsg = repairError instanceof Error ? repairError.message : String(repairError);
- throw new Error(`JSON parsing failed. Original: ${originalMsg}. After attempted repair: ${repairMsg}`);
+ throw new Error(`${ERR_PARSE}: JSON parsing failed. Original: ${originalMsg}. After attempted repair: ${repairMsg}`);
}
}
}
@@ -361,7 +362,7 @@ async function main() {
core.setOutput("output", "");
core.setOutput("output_types", "");
core.setOutput("has_patch", "false");
- core.setFailed(`Agent output ingestion failed: ${errorMsg}`);
+ core.setFailed(`${ERR_API}: Agent output ingestion failed: ${errorMsg}`);
throw error;
}
}
diff --git a/actions/setup/js/create_project.cjs b/actions/setup/js/create_project.cjs
index 59d88ccf22..e5dceffca4 100644
--- a/actions/setup/js/create_project.cjs
+++ b/actions/setup/js/create_project.cjs
@@ -5,6 +5,7 @@ const { loadAgentOutput } = require("./load_agent_output.cjs");
const { getErrorMessage } = require("./error_helpers.cjs");
const { normalizeTemporaryId, isTemporaryId, generateTemporaryId, getOrGenerateTemporaryId } = require("./temporary_id.cjs");
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
+const { ERR_CONFIG, ERR_NOT_FOUND, ERR_VALIDATION } = require("./error_codes.cjs");
/**
* Log detailed GraphQL error information
@@ -161,12 +162,12 @@ async function getIssueNodeId(owner, repo, issueNumber) {
*/
function parseProjectUrl(projectUrl) {
if (!projectUrl || typeof projectUrl !== "string") {
- throw new Error(`Invalid project URL: expected string, got ${typeof projectUrl}`);
+ throw new Error(`${ERR_VALIDATION}: Invalid project URL: expected string, got ${typeof projectUrl}`);
}
const match = projectUrl.match(/github\.com\/(users|orgs)\/([^/]+)\/projects\/(\d+)/);
if (!match) {
- throw new Error(`Invalid project URL: "${projectUrl}". Expected format: https://github.com/orgs/myorg/projects/123`);
+ throw new Error(`${ERR_VALIDATION}: Invalid project URL: "${projectUrl}". Expected format: https://github.com/orgs/myorg/projects/123`);
}
return {
@@ -224,12 +225,12 @@ async function createProjectView(projectUrl, viewConfig) {
const name = typeof viewConfig.name === "string" ? viewConfig.name.trim() : "";
if (!name) {
- throw new Error("View name is required and must be a non-empty string");
+ throw new Error(`${ERR_VALIDATION}: View name is required and must be a non-empty string`);
}
const layout = typeof viewConfig.layout === "string" ? viewConfig.layout.trim() : "";
if (!layout || !["table", "board", "roadmap"].includes(layout)) {
- throw new Error(`Invalid view layout "${layout}". Must be one of: table, board, roadmap`);
+ throw new Error(`${ERR_VALIDATION}: Invalid view layout "${layout}". Must be one of: table, board, roadmap`);
}
const filter = typeof viewConfig.filter === "string" ? viewConfig.filter : undefined;
@@ -238,7 +239,7 @@ async function createProjectView(projectUrl, viewConfig) {
if (visibleFields) {
const invalid = visibleFields.filter(v => typeof v !== "number" || !Number.isFinite(v));
if (invalid.length > 0) {
- throw new Error(`Invalid visible_fields. Must be an array of numbers (field IDs). Invalid values: ${invalid.map(v => JSON.stringify(v)).join(", ")}`);
+ throw new Error(`${ERR_VALIDATION}: Invalid visible_fields. Must be an array of numbers (field IDs). Invalid values: ${invalid.map(v => JSON.stringify(v)).join(", ")}`);
}
}
@@ -305,7 +306,7 @@ async function main(config = {}, githubClient = null) {
const github = githubClient || global.github;
if (!github) {
- throw new Error("GitHub client is required but not provided. Either pass a github client to main() or ensure global.github is set by github-script action.");
+ throw new Error(`${ERR_CONFIG}: GitHub client is required but not provided. Either pass a github client to main() or ensure global.github is set by github-script action.`);
}
if (defaultTargetOwner) {
@@ -378,7 +379,7 @@ async function main(config = {}, githubClient = null) {
core.info(`Resolved temporary ID ${tempIdStr} in item_url to ${resolvedUrl}`);
item_url = resolvedUrl;
} else {
- throw new Error(`Temporary ID '${tempIdStr}' in item_url not found. Ensure create_issue was called before create_project.`);
+ throw new Error(`${ERR_NOT_FOUND}: Temporary ID '${tempIdStr}' in item_url not found. Ensure create_issue was called before create_project.`);
}
}
}
@@ -399,14 +400,14 @@ async function main(config = {}, githubClient = null) {
title = `${titlePrefix} #${issueNumber}`;
core.info(`Generated title from issue number: "${title}"`);
} else {
- throw new Error("Missing required field 'title' in create_project call and unable to generate from context");
+ throw new Error(`${ERR_VALIDATION}: Missing required field 'title' in create_project call and unable to generate from context`);
}
}
// Determine owner - use explicit owner, default, or error
const targetOwner = owner || defaultTargetOwner;
if (!targetOwner) {
- throw new Error("No owner specified and no default target-owner configured. Either provide 'owner' field or configure 'target-owner' in safe-outputs.create-project");
+ throw new Error(`${ERR_VALIDATION}: No owner specified and no default target-owner configured. Either provide 'owner' field or configure 'target-owner' in safe-outputs.create-project`);
}
// Determine owner type (org or user)
diff --git a/actions/setup/js/create_project_status_update.cjs b/actions/setup/js/create_project_status_update.cjs
index 9100c79c09..65e6d70949 100644
--- a/actions/setup/js/create_project_status_update.cjs
+++ b/actions/setup/js/create_project_status_update.cjs
@@ -5,6 +5,7 @@ const { loadAgentOutput } = require("./load_agent_output.cjs");
const { getErrorMessage } = require("./error_helpers.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
+const { ERR_CONFIG, ERR_NOT_FOUND, ERR_PARSE, ERR_VALIDATION } = require("./error_codes.cjs");
/**
* @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction
@@ -57,12 +58,12 @@ function logGraphQLError(error, operation) {
*/
function parseProjectUrl(projectUrl) {
if (!projectUrl || typeof projectUrl !== "string") {
- throw new Error(`Invalid project input: expected string, got ${typeof projectUrl}. The "project" field is required and must be a full GitHub project URL.`);
+ throw new Error(`${ERR_VALIDATION}: Invalid project input: expected string, got ${typeof projectUrl}. The "project" field is required and must be a full GitHub project URL.`);
}
const match = projectUrl.match(/^https:\/\/[^/]+\/(users|orgs)\/([^/]+)\/projects\/(\d+)/);
if (!match) {
- throw new Error(`Invalid project URL: "${projectUrl}". The "project" field must be a full GitHub project URL (e.g., https://github.com/orgs/myorg/projects/123).`);
+ throw new Error(`${ERR_VALIDATION}: Invalid project URL: "${projectUrl}". The "project" field must be a full GitHub project URL (e.g., https://github.com/orgs/myorg/projects/123).`);
}
return {
@@ -209,7 +210,9 @@ async function resolveProjectV2(projectInfo, projectNumberInt) {
} catch (fallbackError) {
// Both direct query and fallback list query failed - this could be a transient API error
const who = projectInfo.scope === "orgs" ? `org ${projectInfo.ownerLogin}` : `user ${projectInfo.ownerLogin}`;
- throw new Error(`Unable to resolve project #${projectNumberInt} for ${who}. Both direct projectV2 query and fallback projectsV2 list query failed. This may be a transient GitHub API error. Error: ${getErrorMessage(fallbackError)}`);
+ throw new Error(
+ `${ERR_NOT_FOUND}: Unable to resolve project #${projectNumberInt} for ${who}. Both direct projectV2 query and fallback projectsV2 list query failed. This may be a transient GitHub API error. Error: ${getErrorMessage(fallbackError)}`
+ );
}
const nodes = Array.isArray(list.nodes) ? list.nodes : [];
@@ -221,7 +224,7 @@ async function resolveProjectV2(projectInfo, projectNumberInt) {
const total = typeof list.totalCount === "number" ? ` (totalCount=${list.totalCount})` : "";
const who = projectInfo.scope === "orgs" ? `org ${projectInfo.ownerLogin}` : `user ${projectInfo.ownerLogin}`;
- throw new Error(`Project #${projectNumberInt} not found or not accessible for ${who}.${total} Accessible Projects v2: ${summary}`);
+ throw new Error(`${ERR_NOT_FOUND}: Project #${projectNumberInt} not found or not accessible for ${who}.${total} Accessible Projects v2: ${summary}`);
}
/**
@@ -291,7 +294,7 @@ async function main(config = {}, githubClient = null) {
const github = githubClient || global.github;
if (!github) {
- throw new Error("GitHub client is required but not provided. Either pass a github client to main() or ensure global.github is set by github-script action.");
+ throw new Error(`${ERR_CONFIG}: GitHub client is required but not provided. Either pass a github client to main() or ensure global.github is set by github-script action.`);
}
core.info(`Max count: ${maxCount}`);
@@ -351,7 +354,7 @@ async function main(config = {}, githubClient = null) {
const projectNumberInt = parseInt(projectInfo.projectNumber, 10);
if (!Number.isFinite(projectNumberInt)) {
- throw new Error(`Invalid project number parsed from URL: ${projectInfo.projectNumber}`);
+ throw new Error(`${ERR_PARSE}: Invalid project number parsed from URL: ${projectInfo.projectNumber}`);
}
const project = await resolveProjectV2(projectInfo, projectNumberInt);
diff --git a/actions/setup/js/error_codes.cjs b/actions/setup/js/error_codes.cjs
new file mode 100644
index 0000000000..07ac2798e9
--- /dev/null
+++ b/actions/setup/js/error_codes.cjs
@@ -0,0 +1,53 @@
+// @ts-check
+
+/**
+ * Standardized error codes for safe-outputs handlers.
+ *
+ * These codes provide machine-readable prefixes for error messages,
+ * enabling structured logging, monitoring dashboards, and alerting rules.
+ *
+ * Usage:
+ * const { ERR_VALIDATION } = require("./error_codes.cjs");
+ * throw new Error(`${ERR_VALIDATION}: Missing required field: title`);
+ * core.setFailed(`${ERR_CONFIG}: GH_AW_PROMPT environment variable is not set`);
+ *
+ * Error code categories:
+ * ERR_VALIDATION - Input validation failures (missing fields, invalid format, limits exceeded)
+ * ERR_PERMISSION - Authorization and permission check failures
+ * ERR_API - GitHub API call failures
+ * ERR_CONFIG - Configuration errors (missing env vars, bad setup)
+ * ERR_NOT_FOUND - Resource not found (issues, discussions, PRs)
+ * ERR_PARSE - Parsing failures (JSON, NDJSON, log formats)
+ * ERR_SYSTEM - System and I/O errors (file access, git operations)
+ */
+
+/** @type {string} Input validation failures */
+const ERR_VALIDATION = "ERR_VALIDATION";
+
+/** @type {string} Authorization and permission check failures */
+const ERR_PERMISSION = "ERR_PERMISSION";
+
+/** @type {string} GitHub API call failures */
+const ERR_API = "ERR_API";
+
+/** @type {string} Configuration errors (missing env vars, bad setup) */
+const ERR_CONFIG = "ERR_CONFIG";
+
+/** @type {string} Resource not found */
+const ERR_NOT_FOUND = "ERR_NOT_FOUND";
+
+/** @type {string} Parsing failures (JSON, NDJSON, log formats) */
+const ERR_PARSE = "ERR_PARSE";
+
+/** @type {string} System and I/O errors */
+const ERR_SYSTEM = "ERR_SYSTEM";
+
+module.exports = {
+ ERR_VALIDATION,
+ ERR_PERMISSION,
+ ERR_API,
+ ERR_CONFIG,
+ ERR_NOT_FOUND,
+ ERR_PARSE,
+ ERR_SYSTEM,
+};
diff --git a/actions/setup/js/error_recovery.cjs b/actions/setup/js/error_recovery.cjs
index 14da05572e..b3a5fffded 100644
--- a/actions/setup/js/error_recovery.cjs
+++ b/actions/setup/js/error_recovery.cjs
@@ -7,6 +7,7 @@
*/
const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_API } = require("./error_codes.cjs");
/**
* Configuration for retry behavior
@@ -145,13 +146,14 @@ async function withRetry(operation, config = {}, operationName = "operation") {
* @param {number} [context.maxRetries] - Maximum retry attempts
* @param {boolean} context.retryable - Whether the error is retryable
* @param {string} context.suggestion - Suggestion for resolving the error
+ * @param {string} [context.code] - Optional standardized error code (e.g., ERR_API)
* @returns {Error} Enhanced error with context
*/
function enhanceError(error, context) {
const originalMessage = getErrorMessage(error);
const timestamp = new Date().toISOString();
- let enhancedMessage = `[${timestamp}] ${context.operation} failed`;
+ let enhancedMessage = `${context.code || ERR_API}: [${timestamp}] ${context.operation} failed`;
if (context.maxRetries !== undefined) {
enhancedMessage += ` after ${context.maxRetries} retry attempts`;
diff --git a/actions/setup/js/file_helpers.cjs b/actions/setup/js/file_helpers.cjs
index 189a710e33..b797995002 100644
--- a/actions/setup/js/file_helpers.cjs
+++ b/actions/setup/js/file_helpers.cjs
@@ -12,6 +12,7 @@
const fs = require("fs");
const path = require("path");
const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_SYSTEM } = require("./error_codes.cjs");
/**
* List all files recursively in a directory
@@ -72,7 +73,7 @@ function checkFileExists(filePath, artifactDir, fileDescription, required) {
core.info(" Found " + files.length + " file(s):");
files.forEach(file => core.info(" - " + file));
}
- core.setFailed("❌ " + fileDescription + " not found at: " + filePath);
+ core.setFailed(`${ERR_SYSTEM}: ❌ ${fileDescription} not found at: ${filePath}`);
return false;
} else {
core.info("No " + fileDescription.toLowerCase() + " found at: " + filePath);
diff --git a/actions/setup/js/frontmatter_hash_pure.cjs b/actions/setup/js/frontmatter_hash_pure.cjs
index 003afd5cca..bfeae067cd 100644
--- a/actions/setup/js/frontmatter_hash_pure.cjs
+++ b/actions/setup/js/frontmatter_hash_pure.cjs
@@ -3,6 +3,7 @@
const fs = require("fs");
const path = require("path");
const crypto = require("crypto");
+const { ERR_PARSE, ERR_SYSTEM } = require("./error_codes.cjs");
/**
* Default file reader using Node.js fs module
@@ -96,7 +97,7 @@ function extractFrontmatterAndBody(content) {
}
if (endIndex === -1) {
- throw new Error("Frontmatter not properly closed");
+ throw new Error(`${ERR_PARSE}: Frontmatter not properly closed`);
}
const frontmatterText = lines.slice(1, endIndex).join("\n");
@@ -348,7 +349,7 @@ function createGitHubFileReader(github, owner, repo, ref) {
return response.data.content;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
- throw new Error(`Failed to read file ${filePath} from GitHub: ${errorMessage}`);
+ throw new Error(`${ERR_SYSTEM}: Failed to read file ${filePath} from GitHub: ${errorMessage}`);
}
};
}
diff --git a/actions/setup/js/get_current_branch.cjs b/actions/setup/js/get_current_branch.cjs
index b0a5e0f2a2..353ce866a7 100644
--- a/actions/setup/js/get_current_branch.cjs
+++ b/actions/setup/js/get_current_branch.cjs
@@ -2,6 +2,7 @@
///
const { execSync } = require("child_process");
+const { ERR_CONFIG } = require("./error_codes.cjs");
/**
* Get the current git branch name
@@ -36,7 +37,7 @@ function getCurrentBranch() {
return ghRefName;
}
- throw new Error("Failed to determine current branch: git command failed and no GitHub environment variables available");
+ throw new Error(`${ERR_CONFIG}: Failed to determine current branch: git command failed and no GitHub environment variables available`);
}
module.exports = {
diff --git a/actions/setup/js/git_helpers.cjs b/actions/setup/js/git_helpers.cjs
index cff8eea239..f8c94c48d3 100644
--- a/actions/setup/js/git_helpers.cjs
+++ b/actions/setup/js/git_helpers.cjs
@@ -2,6 +2,7 @@
///
const { spawnSync } = require("child_process");
+const { ERR_SYSTEM } = require("./error_codes.cjs");
/**
* Safely execute git command using spawnSync with args array to prevent shell injection
@@ -39,7 +40,7 @@ function execGitSync(args, options = {}) {
}
if (result.status !== 0) {
- const errorMsg = result.stderr || `Git command failed with status ${result.status}`;
+ const errorMsg = `${ERR_SYSTEM}: ${result.stderr || `Git command failed with status ${result.status}`}`;
if (typeof core !== "undefined" && core.error) {
core.error(`Git command failed: ${gitCommand}`);
core.error(`Exit status: ${result.status}`);
diff --git a/actions/setup/js/interpolate_prompt.cjs b/actions/setup/js/interpolate_prompt.cjs
index 1484c8beef..0002a80587 100644
--- a/actions/setup/js/interpolate_prompt.cjs
+++ b/actions/setup/js/interpolate_prompt.cjs
@@ -9,6 +9,7 @@ const fs = require("fs");
const { isTruthy } = require("./is_truthy.cjs");
const { processRuntimeImports } = require("./runtime_import.cjs");
const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_API, ERR_CONFIG, ERR_VALIDATION } = require("./error_codes.cjs");
/**
* Interpolates variables in the prompt content
@@ -141,7 +142,7 @@ async function main() {
const promptPath = process.env.GH_AW_PROMPT;
if (!promptPath) {
- core.setFailed("GH_AW_PROMPT environment variable is not set");
+ core.setFailed(`${ERR_CONFIG}: GH_AW_PROMPT environment variable is not set`);
return;
}
core.info(`[main] Prompt path: ${promptPath}`);
@@ -149,7 +150,7 @@ async function main() {
// Get the workspace directory for runtime imports
const workspaceDir = process.env.GITHUB_WORKSPACE;
if (!workspaceDir) {
- core.setFailed("GITHUB_WORKSPACE environment variable is not set");
+ core.setFailed(`${ERR_CONFIG}: GITHUB_WORKSPACE environment variable is not set`);
return;
}
core.info(`[main] Workspace directory: ${workspaceDir}`);
@@ -256,7 +257,7 @@ async function main() {
if (err.stack) {
core.info(`[main] Stack trace:\n${err.stack}`);
}
- core.setFailed(getErrorMessage(error));
+ core.setFailed(`${ERR_API}: ${getErrorMessage(error)}`);
}
}
diff --git a/actions/setup/js/interpolate_prompt.test.cjs b/actions/setup/js/interpolate_prompt.test.cjs
index f3096787f3..031e6a8e93 100644
--- a/actions/setup/js/interpolate_prompt.test.cjs
+++ b/actions/setup/js/interpolate_prompt.test.cjs
@@ -3,6 +3,7 @@ import fs from "fs";
import path from "path";
import os from "os";
import { fileURLToPath } from "url";
+const { ERR_CONFIG } = require("./error_codes.cjs");
const __filename = fileURLToPath(import.meta.url),
__dirname = path.dirname(__filename),
core = { info: vi.fn(), setFailed: vi.fn() };
@@ -118,7 +119,7 @@ describe("interpolate_prompt", () => {
const mainMatch = interpolatePromptScript.match(/async function main\(\)\s*{[\s\S]*?^}/m);
if (!mainMatch) throw new Error("Could not extract main function");
const main = eval(`(${mainMatch[0]})`);
- (main(), expect(core.setFailed).toHaveBeenCalledWith("GH_AW_PROMPT environment variable is not set"));
+ (main(), expect(core.setFailed).toHaveBeenCalledWith(`${ERR_CONFIG}: GH_AW_PROMPT environment variable is not set`));
}));
}));
});
diff --git a/actions/setup/js/lock-issue.cjs b/actions/setup/js/lock-issue.cjs
index dae96e3395..996789903f 100644
--- a/actions/setup/js/lock-issue.cjs
+++ b/actions/setup/js/lock-issue.cjs
@@ -8,6 +8,7 @@
*/
const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_NOT_FOUND } = require("./error_codes.cjs");
async function main() {
// Log actor and event information for debugging
@@ -17,7 +18,7 @@ async function main() {
const issueNumber = context.issue.number;
if (!issueNumber) {
- core.setFailed("Issue number not found in context");
+ core.setFailed(`${ERR_NOT_FOUND}: Issue number not found in context`);
return;
}
@@ -63,7 +64,7 @@ async function main() {
} catch (error) {
const errorMessage = getErrorMessage(error);
core.error(`Failed to lock issue: ${errorMessage}`);
- core.setFailed(`Failed to lock issue #${issueNumber}: ${errorMessage}`);
+ core.setFailed(`${ERR_NOT_FOUND}: Failed to lock issue #${issueNumber}: ${errorMessage}`);
core.setOutput("locked", "false");
}
}
diff --git a/actions/setup/js/lock-issue.test.cjs b/actions/setup/js/lock-issue.test.cjs
index 39bfa7c74e..0b58fe9150 100644
--- a/actions/setup/js/lock-issue.test.cjs
+++ b/actions/setup/js/lock-issue.test.cjs
@@ -1,6 +1,7 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
import fs from "fs";
import path from "path";
+const { ERR_NOT_FOUND } = require("./error_codes.cjs");
const mockCore = { debug: vi.fn(), info: vi.fn(), warning: vi.fn(), error: vi.fn(), setFailed: vi.fn(), setOutput: vi.fn() },
mockGithub = { rest: { issues: { get: vi.fn(), lock: vi.fn() } } },
mockContext = { eventName: "issues", runId: 12345, repo: { owner: "testowner", repo: "testrepo" }, issue: { number: 42 }, payload: { issue: { number: 42 }, repository: { html_url: "https://github.com/testowner/testrepo" } } };
@@ -51,7 +52,7 @@ const mockCore = { debug: vi.fn(), info: vi.fn(), warning: vi.fn(), error: vi.fn
delete global.context.payload.issue,
await eval(`(async () => { ${lockIssueScript}; await main(); })()`),
expect(mockGithub.rest.issues.lock).not.toHaveBeenCalled(),
- expect(mockCore.setFailed).toHaveBeenCalledWith("Issue number not found in context"));
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_NOT_FOUND}: Issue number not found in context`));
}),
it("should handle API errors gracefully", async () => {
mockGithub.rest.issues.get.mockResolvedValue({ data: { number: 42, locked: !1 } });
@@ -60,7 +61,7 @@ const mockCore = { debug: vi.fn(), info: vi.fn(), warning: vi.fn(), error: vi.fn
await eval(`(async () => { ${lockIssueScript}; await main(); })()`),
expect(mockGithub.rest.issues.lock).toHaveBeenCalled(),
expect(mockCore.error).toHaveBeenCalledWith("Failed to lock issue: API rate limit exceeded"),
- expect(mockCore.setFailed).toHaveBeenCalledWith("Failed to lock issue #42: API rate limit exceeded"),
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_NOT_FOUND}: Failed to lock issue #42: API rate limit exceeded`),
expect(mockCore.setOutput).toHaveBeenCalledWith("locked", "false"));
}),
it("should handle non-Error exceptions", async () => {
@@ -68,7 +69,7 @@ const mockCore = { debug: vi.fn(), info: vi.fn(), warning: vi.fn(), error: vi.fn
mockGithub.rest.issues.lock.mockRejectedValue("String error"),
await eval(`(async () => { ${lockIssueScript}; await main(); })()`),
expect(mockCore.error).toHaveBeenCalledWith("Failed to lock issue: String error"),
- expect(mockCore.setFailed).toHaveBeenCalledWith("Failed to lock issue #42: String error"),
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_NOT_FOUND}: Failed to lock issue #42: String error`),
expect(mockCore.setOutput).toHaveBeenCalledWith("locked", "false"));
}),
it("should work with different issue numbers", async () => {
diff --git a/actions/setup/js/log_parser_bootstrap.cjs b/actions/setup/js/log_parser_bootstrap.cjs
index f6d4e38616..3168b349fe 100644
--- a/actions/setup/js/log_parser_bootstrap.cjs
+++ b/actions/setup/js/log_parser_bootstrap.cjs
@@ -3,6 +3,7 @@
const { generatePlainTextSummary, generateCopilotCliStyleSummary, wrapAgentLogInSection, formatSafeOutputsPreview } = require("./log_parser_shared.cjs");
const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_API, ERR_CONFIG, ERR_VALIDATION } = require("./error_codes.cjs");
/**
* Bootstrap helper for log parser entry points.
@@ -177,21 +178,21 @@ async function runLogParser(options) {
// Claude-specific guardrail: if no structured log entries were parsed, treat as execution failure.
// This catches silent startup failures where Claude exits before producing JSON tool activity.
if (parserName === "Claude" && (!logEntries || logEntries.length === 0)) {
- core.setFailed("Claude execution failed: no structured log entries were produced. This usually indicates a startup or configuration error before tool execution.");
+ core.setFailed(`${ERR_CONFIG}: Claude execution failed: no structured log entries were produced. This usually indicates a startup or configuration error before tool execution.`);
}
// Handle MCP server failures if present
if (mcpFailures && mcpFailures.length > 0) {
const failedServers = mcpFailures.join(", ");
- core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
+ core.setFailed(`${ERR_API}: MCP server(s) failed to launch: ${failedServers}`);
}
// Handle max-turns limit if hit
if (maxTurnsHit) {
- core.setFailed(`Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
+ core.setFailed(`${ERR_VALIDATION}: Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
}
} catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
+ core.setFailed(`${ERR_API}: ${error instanceof Error ? error.message : String(error)}`);
}
}
diff --git a/actions/setup/js/log_parser_bootstrap.test.cjs b/actions/setup/js/log_parser_bootstrap.test.cjs
index 9ce177df88..3e9cd8c9ff 100644
--- a/actions/setup/js/log_parser_bootstrap.test.cjs
+++ b/actions/setup/js/log_parser_bootstrap.test.cjs
@@ -2,6 +2,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
+const { ERR_API, ERR_CONFIG, ERR_VALIDATION } = require("./error_codes.cjs");
const __filename = fileURLToPath(import.meta.url),
__dirname = path.dirname(__filename);
describe("log_parser_bootstrap.cjs", () => {
@@ -70,7 +71,7 @@ describe("log_parser_bootstrap.cjs", () => {
process.env.GH_AW_AGENT_OUTPUT = logFile;
const mockParseLog = vi.fn().mockReturnValue({ markdown: "## Result\n", mcpFailures: [], maxTurnsHit: false, logEntries: [] });
runLogParser({ parseLog: mockParseLog, parserName: "Claude" });
- expect(mockCore.setFailed).toHaveBeenCalledWith("Claude execution failed: no structured log entries were produced. This usually indicates a startup or configuration error before tool execution.");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_CONFIG}: Claude execution failed: no structured log entries were produced. This usually indicates a startup or configuration error before tool execution.`);
} finally {
fs.unlinkSync(logFile);
fs.rmdirSync(tmpDir);
@@ -109,7 +110,7 @@ describe("log_parser_bootstrap.cjs", () => {
logFile = path.join(tmpDir, "test.log");
(fs.writeFileSync(logFile, "content"), (process.env.GH_AW_AGENT_OUTPUT = logFile));
const mockParseLog = vi.fn().mockReturnValue({ markdown: "## Result\n", mcpFailures: ["server1", "server2"], maxTurnsHit: !1 });
- (runLogParser({ parseLog: mockParseLog, parserName: "TestParser" }), expect(mockCore.setFailed).toHaveBeenCalledWith("MCP server(s) failed to launch: server1, server2"), fs.unlinkSync(logFile), fs.rmdirSync(tmpDir));
+ (runLogParser({ parseLog: mockParseLog, parserName: "TestParser" }), expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_API}: MCP server(s) failed to launch: server1, server2`), fs.unlinkSync(logFile), fs.rmdirSync(tmpDir));
}),
it("should handle max-turns limit reached", () => {
const tmpDir = fs.mkdtempSync(path.join(__dirname, "test-")),
@@ -117,7 +118,7 @@ describe("log_parser_bootstrap.cjs", () => {
(fs.writeFileSync(logFile, "content"), (process.env.GH_AW_AGENT_OUTPUT = logFile));
const mockParseLog = vi.fn().mockReturnValue({ markdown: "## Result\n", mcpFailures: [], maxTurnsHit: !0 });
(runLogParser({ parseLog: mockParseLog, parserName: "TestParser" }),
- expect(mockCore.setFailed).toHaveBeenCalledWith("Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully."),
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_VALIDATION}: Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`),
fs.unlinkSync(logFile),
fs.rmdirSync(tmpDir));
}),
@@ -159,7 +160,7 @@ describe("log_parser_bootstrap.cjs", () => {
const mockParseLog = vi.fn().mockImplementation(() => {
throw new Error("Parser error");
});
- (runLogParser({ parseLog: mockParseLog, parserName: "TestParser" }), expect(mockCore.setFailed).toHaveBeenCalledWith(expect.any(Error)), fs.unlinkSync(logFile), fs.rmdirSync(tmpDir));
+ (runLogParser({ parseLog: mockParseLog, parserName: "TestParser" }), expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_API}: Parser error`), fs.unlinkSync(logFile), fs.rmdirSync(tmpDir));
}),
it("should handle failed parse (empty result)", () => {
const tmpDir = fs.mkdtempSync(path.join(__dirname, "test-")),
diff --git a/actions/setup/js/log_parser_shared.cjs b/actions/setup/js/log_parser_shared.cjs
index e94300d7d6..74b7a467f3 100644
--- a/actions/setup/js/log_parser_shared.cjs
+++ b/actions/setup/js/log_parser_shared.cjs
@@ -3,6 +3,7 @@
const { getErrorMessage } = require("./error_helpers.cjs");
const { unfenceMarkdown } = require("./markdown_unfencing.cjs");
+const { ERR_PARSE } = require("./error_codes.cjs");
/**
* Shared utility functions for log parsers
@@ -837,7 +838,7 @@ function parseLogEntries(logContent) {
try {
logEntries = JSON.parse(logContent);
if (!Array.isArray(logEntries) || logEntries.length === 0) {
- throw new Error("Not a JSON array or empty array");
+ throw new Error(`${ERR_PARSE}: Not a JSON array or empty array`);
}
return logEntries;
} catch (jsonArrayError) {
diff --git a/actions/setup/js/mark_pull_request_as_ready_for_review.cjs b/actions/setup/js/mark_pull_request_as_ready_for_review.cjs
index e722454429..4b2fdfda40 100644
--- a/actions/setup/js/mark_pull_request_as_ready_for_review.cjs
+++ b/actions/setup/js/mark_pull_request_as_ready_for_review.cjs
@@ -9,6 +9,7 @@ const { generateFooterWithMessages } = require("./messages_footer.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");
const { getErrorMessage } = require("./error_helpers.cjs");
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
+const { ERR_NOT_FOUND } = require("./error_codes.cjs");
/** @type {string} Safe output type handled by this module */
const HANDLER_TYPE = "mark_pull_request_as_ready_for_review";
@@ -29,7 +30,7 @@ async function getPullRequestDetails(github, owner, repo, prNumber) {
});
if (!pr) {
- throw new Error(`Pull request #${prNumber} not found in ${owner}/${repo}`);
+ throw new Error(`${ERR_NOT_FOUND}: Pull request #${prNumber} not found in ${owner}/${repo}`);
}
return pr;
diff --git a/actions/setup/js/mcp_http_transport.cjs b/actions/setup/js/mcp_http_transport.cjs
index 044a756b49..4d4baa41ba 100644
--- a/actions/setup/js/mcp_http_transport.cjs
+++ b/actions/setup/js/mcp_http_transport.cjs
@@ -27,6 +27,7 @@ moduleLogger.debug("Module is being loaded");
const http = require("http");
const { randomUUID } = require("crypto");
const { createServer, registerTool, handleRequest } = require("./mcp_server_core.cjs");
+const { ERR_SYSTEM } = require("./error_codes.cjs");
/**
* Simple MCP Server wrapper that provides a class-like interface
@@ -140,7 +141,7 @@ class MCPHTTPTransport {
const logger = createLogger("MCPHTTPTransport");
logger.debug(`Called, started=${this.started}`);
if (this.started) {
- throw new Error("Transport already started");
+ throw new Error(`${ERR_SYSTEM}: Transport already started`);
}
this.started = true;
logger.debug("Set started=true");
diff --git a/actions/setup/js/mcp_server_core.cjs b/actions/setup/js/mcp_server_core.cjs
index dd8f6dd9b0..2ee94cd1d5 100644
--- a/actions/setup/js/mcp_server_core.cjs
+++ b/actions/setup/js/mcp_server_core.cjs
@@ -3,6 +3,7 @@
const { createLogger } = require("./mcp_logger.cjs");
const moduleLogger = createLogger("mcp_server_core");
+const { ERR_VALIDATION } = require("./error_codes.cjs");
// Log immediately at module load time
moduleLogger.debug("Module is being loaded");
@@ -802,7 +803,7 @@ function start(server, options = {}) {
server.debug(` tools: ${Object.keys(server.tools).join(", ")}`);
if (!Object.keys(server.tools).length) {
- throw new Error("No tools registered");
+ throw new Error(`${ERR_VALIDATION}: No tools registered`);
}
const onData = async chunk => {
diff --git a/actions/setup/js/merge_remote_agent_github_folder.cjs b/actions/setup/js/merge_remote_agent_github_folder.cjs
index 77a0a58356..2e026ff61f 100644
--- a/actions/setup/js/merge_remote_agent_github_folder.cjs
+++ b/actions/setup/js/merge_remote_agent_github_folder.cjs
@@ -23,6 +23,7 @@ const path = require("path");
const { execFileSync } = require("child_process");
const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_CONFIG, ERR_PARSE, ERR_SYSTEM, ERR_VALIDATION } = require("./error_codes.cjs");
// Get the core object - in github-script context it's global, for testing we create a minimal version
const coreObj =
@@ -133,7 +134,7 @@ function validateGitParameter(value, name) {
// This is safe for git owner/repo/ref names
const safePattern = /^[a-zA-Z0-9._/-]+$/;
if (!safePattern.test(value)) {
- throw new Error(`Invalid ${name}: contains unsafe characters. Only alphanumeric, hyphens, underscores, dots, and forward slashes are allowed.`);
+ throw new Error(`${ERR_VALIDATION}: Invalid ${name}: contains unsafe characters. Only alphanumeric, hyphens, underscores, dots, and forward slashes are allowed.`);
}
}
@@ -147,12 +148,12 @@ function validateGitParameter(value, name) {
function validateSafePath(userPath, basePath, name) {
// Reject paths with null bytes
if (userPath.includes("\0")) {
- throw new Error(`Invalid ${name}: contains null bytes`);
+ throw new Error(`${ERR_VALIDATION}: Invalid ${name}: contains null bytes`);
}
// Reject paths that attempt to traverse up (..)
if (userPath.includes("..")) {
- throw new Error(`Invalid ${name}: path traversal detected`);
+ throw new Error(`${ERR_VALIDATION}: Invalid ${name}: path traversal detected`);
}
// Resolve the full path and ensure it's within the base path
@@ -160,7 +161,7 @@ function validateSafePath(userPath, basePath, name) {
const resolvedBase = path.resolve(basePath);
if (!resolvedPath.startsWith(resolvedBase + path.sep) && resolvedPath !== resolvedBase) {
- throw new Error(`Invalid ${name}: path escapes base directory`);
+ throw new Error(`${ERR_VALIDATION}: Invalid ${name}: path escapes base directory`);
}
return resolvedPath;
@@ -212,7 +213,7 @@ function sparseCheckoutGithubFolder(owner, repo, ref, tempDir) {
coreObj.info("Sparse checkout completed successfully");
} catch (error) {
- throw new Error(`Sparse checkout failed: ${getErrorMessage(error)}`);
+ throw new Error(`${ERR_PARSE}: Sparse checkout failed: ${getErrorMessage(error)}`);
}
}
@@ -302,7 +303,7 @@ async function mergeRepositoryGithubFolder(owner, repo, ref, workspace) {
// Check if the pre-checked-out folder exists
if (!pathExists(checkoutPath)) {
- throw new Error(`Pre-checked-out repository not found at ${checkoutPath}. The actions/checkout step may have failed.`);
+ throw new Error(`${ERR_SYSTEM}: Pre-checked-out repository not found at ${checkoutPath}. The actions/checkout step may have failed.`);
}
// Check if .github folder exists in the checked-out repository
@@ -329,7 +330,7 @@ async function mergeRepositoryGithubFolder(owner, repo, ref, workspace) {
for (const conflict of conflicts) {
coreObj.error(` - ${conflict}`);
}
- throw new Error(`Cannot merge .github folder from ${owner}/${repo}@${ref}: ${conflicts.length} file(s) conflict with existing files`);
+ throw new Error(`${ERR_VALIDATION}: Cannot merge .github folder from ${owner}/${repo}@${ref}: ${conflicts.length} file(s) conflict with existing files`);
}
if (merged > 0) {
@@ -356,17 +357,17 @@ async function main() {
try {
repositoryImports = JSON.parse(repositoryImportsEnv);
} catch (error) {
- throw new Error(`Failed to parse GH_AW_REPOSITORY_IMPORTS: ${getErrorMessage(error)}`);
+ throw new Error(`${ERR_PARSE}: Failed to parse GH_AW_REPOSITORY_IMPORTS: ${getErrorMessage(error)}`);
}
if (!Array.isArray(repositoryImports)) {
- throw new Error("GH_AW_REPOSITORY_IMPORTS must be a JSON array");
+ throw new Error(`${ERR_PARSE}: GH_AW_REPOSITORY_IMPORTS must be a JSON array`);
}
// Get workspace path
const workspace = process.env.GITHUB_WORKSPACE;
if (!workspace) {
- throw new Error("GITHUB_WORKSPACE environment variable not set");
+ throw new Error(`${ERR_CONFIG}: GITHUB_WORKSPACE environment variable not set`);
}
// Process each repository import
@@ -419,7 +420,7 @@ async function main() {
// Get workspace path
const workspace = process.env.GITHUB_WORKSPACE;
if (!workspace) {
- throw new Error("GITHUB_WORKSPACE environment variable not set");
+ throw new Error(`${ERR_CONFIG}: GITHUB_WORKSPACE environment variable not set`);
}
await mergeRepositoryGithubFolder(owner, repo, ref, workspace);
diff --git a/actions/setup/js/notify_comment_error.cjs b/actions/setup/js/notify_comment_error.cjs
index 9622f1c9e7..8a416200cb 100644
--- a/actions/setup/js/notify_comment_error.cjs
+++ b/actions/setup/js/notify_comment_error.cjs
@@ -10,6 +10,7 @@ const { getRunSuccessMessage, getRunFailureMessage, getDetectionFailureMessage }
const { getMessages } = require("./messages_core.cjs");
const { getErrorMessage, isLockedError } = require("./error_helpers.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");
+const { ERR_VALIDATION } = require("./error_codes.cjs");
/**
* Collect generated asset URLs from safe output jobs
@@ -105,7 +106,7 @@ async function main() {
// At this point, we have a comment to update
if (!runUrl) {
- core.setFailed("Run URL is required");
+ core.setFailed(`${ERR_VALIDATION}: Run URL is required`);
return;
}
@@ -261,7 +262,7 @@ async function main() {
// At this point, we must have a comment ID (verified by earlier checks)
if (!commentId) {
- core.setFailed("Comment ID is required for updating existing comment");
+ core.setFailed(`${ERR_VALIDATION}: Comment ID is required for updating existing comment`);
return;
}
diff --git a/actions/setup/js/notify_comment_error.test.cjs b/actions/setup/js/notify_comment_error.test.cjs
index 327e7aafee..58a87a3083 100644
--- a/actions/setup/js/notify_comment_error.test.cjs
+++ b/actions/setup/js/notify_comment_error.test.cjs
@@ -1,6 +1,7 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import fs from "fs";
import path from "path";
+const { ERR_VALIDATION } = require("./error_codes.cjs");
const mockCore = {
debug: vi.fn(),
info: vi.fn(),
@@ -113,7 +114,7 @@ const mockCore = {
(process.env.GH_AW_WORKFLOW_NAME = "test-workflow"),
(process.env.GH_AW_AGENT_CONCLUSION = "failure"),
await eval(`(async () => { ${notifyCommentScript}; await main(); })()`),
- expect(mockCore.setFailed).toHaveBeenCalledWith("Run URL is required"),
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_VALIDATION}: Run URL is required`),
expect(mockGithub.request).not.toHaveBeenCalled(),
expect(mockGithub.graphql).not.toHaveBeenCalled());
});
diff --git a/actions/setup/js/parse_claude_log.test.cjs b/actions/setup/js/parse_claude_log.test.cjs
index 3c6a77c817..8d69c30d1c 100644
--- a/actions/setup/js/parse_claude_log.test.cjs
+++ b/actions/setup/js/parse_claude_log.test.cjs
@@ -1,6 +1,7 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import fs from "fs";
import path from "path";
+const { ERR_API, ERR_CONFIG, ERR_VALIDATION } = require("./error_codes.cjs");
describe("parse_claude_log.cjs", () => {
let mockCore, originalConsole, originalProcess;
@@ -407,7 +408,7 @@ describe("parse_claude_log.cjs", () => {
expect(mockCore.summary.addRaw).toHaveBeenCalled();
expect(mockCore.summary.write).toHaveBeenCalled();
- expect(mockCore.setFailed).toHaveBeenCalledWith("MCP server(s) failed to launch: broken_server");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_API}: MCP server(s) failed to launch: broken_server`);
});
it("should call setFailed when max-turns limit is hit", async () => {
@@ -421,7 +422,7 @@ describe("parse_claude_log.cjs", () => {
expect(mockCore.summary.addRaw).toHaveBeenCalled();
expect(mockCore.summary.write).toHaveBeenCalled();
- expect(mockCore.setFailed).toHaveBeenCalledWith("Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_VALIDATION}: Agent execution stopped: max-turns limit reached. The agent did not complete its task successfully.`);
});
it("should handle missing log file", async () => {
@@ -440,7 +441,7 @@ describe("parse_claude_log.cjs", () => {
it("should fail when Claude log has no structured entries", async () => {
await runScript("this is not structured Claude JSON output");
- expect(mockCore.setFailed).toHaveBeenCalledWith("Claude execution failed: no structured log entries were produced. This usually indicates a startup or configuration error before tool execution.");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_CONFIG}: Claude execution failed: no structured log entries were produced. This usually indicates a startup or configuration error before tool execution.`);
});
});
diff --git a/actions/setup/js/parse_copilot_log.cjs b/actions/setup/js/parse_copilot_log.cjs
index 15dbb76d7c..48223d8a00 100644
--- a/actions/setup/js/parse_copilot_log.cjs
+++ b/actions/setup/js/parse_copilot_log.cjs
@@ -2,6 +2,7 @@
///
const { createEngineLogParser, generateConversationMarkdown, generateInformationSection, formatInitializationSummary, formatToolUse, parseLogEntries } = require("./log_parser_shared.cjs");
+const { ERR_PARSE } = require("./error_codes.cjs");
const main = createEngineLogParser({
parserName: "Copilot",
@@ -45,7 +46,7 @@ function parseCopilotLog(logContent) {
try {
logEntries = JSON.parse(logContent);
if (!Array.isArray(logEntries)) {
- throw new Error("Not a JSON array");
+ throw new Error(`${ERR_PARSE}: Not a JSON array`);
}
} catch (jsonArrayError) {
// If that fails, try to parse as debug logs format
diff --git a/actions/setup/js/parse_firewall_logs.cjs b/actions/setup/js/parse_firewall_logs.cjs
index 32dce7dfab..fc69fdadce 100644
--- a/actions/setup/js/parse_firewall_logs.cjs
+++ b/actions/setup/js/parse_firewall_logs.cjs
@@ -4,6 +4,7 @@
const fs = require("fs");
const path = require("path");
const { sanitizeWorkflowName } = require("./sanitize_workflow_name.cjs");
+const { ERR_PARSE } = require("./error_codes.cjs");
/**
* Parses firewall logs and creates a step summary
@@ -90,7 +91,7 @@ async function main() {
core.summary.addRaw(summary).write();
core.info("Firewall log summary generated successfully");
} catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
+ core.setFailed(`${ERR_PARSE}: ${error instanceof Error ? error.message : String(error)}`);
}
}
diff --git a/actions/setup/js/parse_mcp_gateway_log.cjs b/actions/setup/js/parse_mcp_gateway_log.cjs
index 7247c41465..3d7f36f4ba 100644
--- a/actions/setup/js/parse_mcp_gateway_log.cjs
+++ b/actions/setup/js/parse_mcp_gateway_log.cjs
@@ -4,6 +4,7 @@
const fs = require("fs");
const { getErrorMessage } = require("./error_helpers.cjs");
const { displayDirectories } = require("./display_file_helpers.cjs");
+const { ERR_PARSE } = require("./error_codes.cjs");
/**
* Parses MCP gateway logs and creates a step summary
@@ -81,7 +82,7 @@ async function main() {
const summary = generateGatewayLogSummary(gatewayLogContent, stderrLogContent);
core.summary.addRaw(summary).write();
} catch (error) {
- core.setFailed(getErrorMessage(error));
+ core.setFailed(`${ERR_PARSE}: ${getErrorMessage(error)}`);
}
}
diff --git a/actions/setup/js/parse_safe_inputs_logs.cjs b/actions/setup/js/parse_safe_inputs_logs.cjs
index ab2b85cbbc..21387f80da 100644
--- a/actions/setup/js/parse_safe_inputs_logs.cjs
+++ b/actions/setup/js/parse_safe_inputs_logs.cjs
@@ -4,6 +4,7 @@
const fs = require("fs");
const path = require("path");
const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_PARSE } = require("./error_codes.cjs");
/**
* Parses safe-inputs MCP server logs and creates a step summary
@@ -64,7 +65,7 @@ async function main() {
const summary = generateSafeInputsSummary(allLogEntries);
core.summary.addRaw(summary).write();
} catch (error) {
- core.setFailed(getErrorMessage(error));
+ core.setFailed(`${ERR_PARSE}: ${getErrorMessage(error)}`);
}
}
diff --git a/actions/setup/js/parse_safe_inputs_logs.test.cjs b/actions/setup/js/parse_safe_inputs_logs.test.cjs
index 8dd30f3890..8f1cc4c33e 100644
--- a/actions/setup/js/parse_safe_inputs_logs.test.cjs
+++ b/actions/setup/js/parse_safe_inputs_logs.test.cjs
@@ -1,6 +1,7 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import fs from "fs";
import path from "path";
+const { ERR_PARSE } = require("./error_codes.cjs");
describe("parse_safe_inputs_logs.cjs", () => {
let mockCore, originalConsole;
@@ -415,7 +416,7 @@ describe("parse_safe_inputs_logs.cjs", () => {
await main();
- expect(mockCore.setFailed).toHaveBeenCalledWith("Test error");
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_PARSE}: Test error`);
});
it("should process multiple log files", async () => {
diff --git a/actions/setup/js/parse_threat_detection_results.cjs b/actions/setup/js/parse_threat_detection_results.cjs
index 102592e8ec..e064c35e4b 100644
--- a/actions/setup/js/parse_threat_detection_results.cjs
+++ b/actions/setup/js/parse_threat_detection_results.cjs
@@ -15,6 +15,7 @@ const path = require("path");
const { getErrorMessage } = require("./error_helpers.cjs");
const { listFilesRecursively } = require("./file_helpers.cjs");
const { AGENT_OUTPUT_FILENAME } = require("./constants.cjs");
+const { ERR_SYSTEM, ERR_VALIDATION } = require("./error_codes.cjs");
/**
* Main entry point for parsing threat detection results
@@ -40,7 +41,7 @@ async function main() {
core.info(" Found " + files.length + " file(s):");
files.forEach(file => core.info(" - " + file));
}
- core.setFailed("❌ Agent output file not found at: " + outputPath);
+ core.setFailed(`${ERR_SYSTEM}: ❌ Agent output file not found at: ${outputPath}`);
return;
}
const outputContent = fs.readFileSync(outputPath, "utf8");
@@ -71,7 +72,7 @@ async function main() {
// Set success output to false before failing
core.setOutput("success", "false");
- core.setFailed("❌ Security threats detected: " + threats.join(", ") + reasonsText);
+ core.setFailed(`${ERR_VALIDATION}: ❌ Security threats detected: ${threats.join(", ")}${reasonsText}`);
} else {
core.info("✅ No security threats detected. Safe outputs may proceed.");
// Set success output to true when no threats detected
diff --git a/actions/setup/js/read_buffer.cjs b/actions/setup/js/read_buffer.cjs
index 1c70a28582..57dcbc6f98 100644
--- a/actions/setup/js/read_buffer.cjs
+++ b/actions/setup/js/read_buffer.cjs
@@ -2,6 +2,7 @@
///
const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_PARSE } = require("./error_codes.cjs");
/**
* ReadBuffer Module
@@ -59,7 +60,7 @@ class ReadBuffer {
try {
return JSON.parse(line);
} catch (error) {
- throw new Error(`Parse error: ${getErrorMessage(error)}`);
+ throw new Error(`${ERR_PARSE}: Parse error: ${getErrorMessage(error)}`);
}
}
}
diff --git a/actions/setup/js/redact_secrets.cjs b/actions/setup/js/redact_secrets.cjs
index 738cc745a9..2340269dcb 100644
--- a/actions/setup/js/redact_secrets.cjs
+++ b/actions/setup/js/redact_secrets.cjs
@@ -8,6 +8,8 @@
*/
const fs = require("fs");
const path = require("path");
+const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_VALIDATION } = require("./error_codes.cjs");
/**
* Recursively finds all files matching the specified extensions
* @param {string} dir - Directory to search
@@ -216,10 +218,8 @@ async function main() {
core.info("Secret redaction complete: no secrets found");
}
} catch (error) {
- core.setFailed(`Secret redaction failed: ${getErrorMessage(error)}`);
+ core.setFailed(`${ERR_VALIDATION}: Secret redaction failed: ${getErrorMessage(error)}`);
}
}
-const { getErrorMessage } = require("./error_helpers.cjs");
-
module.exports = { main, redactSecrets, redactBuiltInPatterns, BUILT_IN_PATTERNS };
diff --git a/actions/setup/js/render_template.cjs b/actions/setup/js/render_template.cjs
index f309beab9d..ae3630e9cc 100644
--- a/actions/setup/js/render_template.cjs
+++ b/actions/setup/js/render_template.cjs
@@ -8,6 +8,7 @@
const { getErrorMessage } = require("./error_helpers.cjs");
const fs = require("fs");
+const { ERR_API, ERR_CONFIG, ERR_VALIDATION } = require("./error_codes.cjs");
/**
* Determines if a value is truthy according to template logic
@@ -144,7 +145,7 @@ function main() {
const promptPath = process.env.GH_AW_PROMPT;
if (!promptPath) {
if (typeof core !== "undefined") {
- core.setFailed("GH_AW_PROMPT environment variable is not set");
+ core.setFailed(`${ERR_CONFIG}: GH_AW_PROMPT environment variable is not set`);
}
process.exit(1);
}
@@ -216,7 +217,7 @@ function main() {
if (err.stack) {
core.info(`[main] Stack trace:\n${err.stack}`);
}
- core.setFailed(getErrorMessage(error));
+ core.setFailed(`${ERR_API}: ${getErrorMessage(error)}`);
} else {
throw error;
}
diff --git a/actions/setup/js/repo_helpers.cjs b/actions/setup/js/repo_helpers.cjs
index 5e373e4a25..a156255539 100644
--- a/actions/setup/js/repo_helpers.cjs
+++ b/actions/setup/js/repo_helpers.cjs
@@ -7,6 +7,7 @@
*/
const { globPatternToRegex } = require("./glob_pattern_helpers.cjs");
+const { ERR_VALIDATION } = require("./error_codes.cjs");
/**
* Parse the allowed repos from config value (array or comma-separated string)
@@ -159,7 +160,7 @@ function resolveAndValidateRepo(item, defaultTargetRepo, allowedRepos, operation
// When valid is false, error is guaranteed to be non-null
const errorMessage = repoValidation.error;
if (!errorMessage) {
- throw new Error("Internal error: repoValidation.error should not be null when valid is false");
+ throw new Error(`${ERR_VALIDATION}: Internal error: repoValidation.error should not be null when valid is false`);
}
return {
success: false,
diff --git a/actions/setup/js/runtime_import.cjs b/actions/setup/js/runtime_import.cjs
index 79d9e08cca..194400202d 100644
--- a/actions/setup/js/runtime_import.cjs
+++ b/actions/setup/js/runtime_import.cjs
@@ -7,6 +7,7 @@
// Also processes inline @path and @url references.
const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_API, ERR_CONFIG, ERR_PARSE, ERR_SYSTEM, ERR_VALIDATION } = require("./error_codes.cjs");
const fs = require("fs");
const path = require("path");
@@ -412,7 +413,7 @@ function processExpressions(content, source) {
// If any unsafe expressions found, throw error
if (unsafeExpressions.length > 0) {
const errorMsg =
- `${source} contains unauthorized GitHub Actions expressions:\n` +
+ `${ERR_VALIDATION}: ${source} contains unauthorized GitHub Actions expressions:\n` +
unsafeExpressions.map(e => ` - ${e}`).join("\n") +
"\n\n" +
"Only expressions from the safe list can be used in runtime imports.\n" +
@@ -541,13 +542,13 @@ async function processUrlImport(url, optional, startLine, endLine) {
const end = endLine !== undefined ? endLine : totalLines;
if (start < 1 || start > totalLines) {
- throw new Error(`Invalid start line ${start} for URL ${url} (total lines: ${totalLines})`);
+ throw new Error(`${ERR_VALIDATION}: Invalid start line ${start} for URL ${url} (total lines: ${totalLines})`);
}
if (end < 1 || end > totalLines) {
- throw new Error(`Invalid end line ${end} for URL ${url} (total lines: ${totalLines})`);
+ throw new Error(`${ERR_VALIDATION}: Invalid end line ${end} for URL ${url} (total lines: ${totalLines})`);
}
if (start > end) {
- throw new Error(`Start line ${start} cannot be greater than end line ${end} for URL ${url}`);
+ throw new Error(`${ERR_VALIDATION}: Start line ${start} cannot be greater than end line ${end} for URL ${url}`);
}
// Extract lines (convert to 0-indexed)
@@ -759,11 +760,11 @@ async function processRuntimeImport(filepathOrUrl, optional, workspaceDir, start
// Security check: ensure the resolved path is within the workspace
const relativePath = path.relative(normalizedBaseFolder, normalizedPath);
if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
- throw new Error(`Security: Path ${filepathOrUrl} must be within workspace (resolves to: ${relativePath})`);
+ throw new Error(`${ERR_CONFIG}: Security: Path ${filepathOrUrl} must be within workspace (resolves to: ${relativePath})`);
}
// Additional check: ensure path stays within .agents folder
if (!relativePath.startsWith(".agents" + path.sep) && relativePath !== ".agents") {
- throw new Error(`Security: Path ${filepathOrUrl} must be within .agents folder`);
+ throw new Error(`${ERR_VALIDATION}: Security: Path ${filepathOrUrl} must be within .agents folder`);
}
} else {
// Regular paths resolve within .github folder
@@ -776,7 +777,7 @@ async function processRuntimeImport(filepathOrUrl, optional, workspaceDir, start
// Security check: ensure the resolved path is within the .github folder
const relativePath = path.relative(normalizedBaseFolder, normalizedPath);
if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
- throw new Error(`Security: Path ${filepathOrUrl} must be within .github folder (resolves to: ${relativePath})`);
+ throw new Error(`${ERR_VALIDATION}: Security: Path ${filepathOrUrl} must be within .github folder (resolves to: ${relativePath})`);
}
}
@@ -786,7 +787,7 @@ async function processRuntimeImport(filepathOrUrl, optional, workspaceDir, start
core.warning(`Optional runtime import file not found: ${filepath}`);
return "";
}
- throw new Error(`Runtime import file not found: ${filepath}`);
+ throw new Error(`${ERR_SYSTEM}: Runtime import file not found: ${filepath}`);
}
// Read the file
@@ -802,13 +803,13 @@ async function processRuntimeImport(filepathOrUrl, optional, workspaceDir, start
const end = endLine !== undefined ? endLine : totalLines;
if (start < 1 || start > totalLines) {
- throw new Error(`Invalid start line ${start} for file ${filepath} (total lines: ${totalLines})`);
+ throw new Error(`${ERR_VALIDATION}: Invalid start line ${start} for file ${filepath} (total lines: ${totalLines})`);
}
if (end < 1 || end > totalLines) {
- throw new Error(`Invalid end line ${end} for file ${filepath} (total lines: ${totalLines})`);
+ throw new Error(`${ERR_VALIDATION}: Invalid end line ${end} for file ${filepath} (total lines: ${totalLines})`);
}
if (start > end) {
- throw new Error(`Start line ${start} cannot be greater than end line ${end} for file ${filepath}`);
+ throw new Error(`${ERR_VALIDATION}: Start line ${start} cannot be greater than end line ${end} for file ${filepath}`);
}
// Extract lines (convert to 0-indexed)
@@ -929,7 +930,7 @@ async function processRuntimeImports(content, workspaceDir, importedFiles = new
// Check for circular dependencies
if (importStack.includes(filepathWithRange)) {
const cycle = [...importStack, filepathWithRange].join(" -> ");
- throw new Error(`Circular dependency detected: ${cycle}`);
+ throw new Error(`${ERR_PARSE}: Circular dependency detected: ${cycle}`);
}
// Add to import stack for circular dependency detection
@@ -953,7 +954,7 @@ async function processRuntimeImports(content, workspaceDir, importedFiles = new
processedContent = processedContent.replace(fullMatch, importedContent);
} catch (error) {
const errorMessage = getErrorMessage(error);
- throw new Error(`Failed to process runtime import for ${filepathWithRange}: ${errorMessage}`);
+ throw new Error(`${ERR_API}: Failed to process runtime import for ${filepathWithRange}: ${errorMessage}`);
} finally {
// Remove from import stack
importStack.pop();
diff --git a/actions/setup/js/safe_inputs_config_loader.cjs b/actions/setup/js/safe_inputs_config_loader.cjs
index 33836c2e0e..da494c49ac 100644
--- a/actions/setup/js/safe_inputs_config_loader.cjs
+++ b/actions/setup/js/safe_inputs_config_loader.cjs
@@ -8,6 +8,7 @@
*/
const fs = require("fs");
+const { ERR_SYSTEM, ERR_VALIDATION } = require("./error_codes.cjs");
/**
* @typedef {Object} SafeInputsToolConfig
@@ -34,7 +35,7 @@ const fs = require("fs");
*/
function loadConfig(configPath) {
if (!fs.existsSync(configPath)) {
- throw new Error(`Configuration file not found: ${configPath}`);
+ throw new Error(`${ERR_SYSTEM}: Configuration file not found: ${configPath}`);
}
const configContent = fs.readFileSync(configPath, "utf-8");
@@ -42,7 +43,7 @@ function loadConfig(configPath) {
// Validate required fields
if (!config.tools || !Array.isArray(config.tools)) {
- throw new Error("Configuration must contain a 'tools' array");
+ throw new Error(`${ERR_VALIDATION}: Configuration must contain a 'tools' array`);
}
return config;
diff --git a/actions/setup/js/safe_inputs_mcp_server_http.cjs b/actions/setup/js/safe_inputs_mcp_server_http.cjs
index 85029ee4e8..b703bdcd0d 100644
--- a/actions/setup/js/safe_inputs_mcp_server_http.cjs
+++ b/actions/setup/js/safe_inputs_mcp_server_http.cjs
@@ -30,6 +30,7 @@ const { generateEnhancedErrorMessage } = require("./mcp_enhanced_errors.cjs");
const { createLogger } = require("./mcp_logger.cjs");
const { bootstrapSafeInputsServer, cleanupConfigFile } = require("./safe_inputs_bootstrap.cjs");
const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_VALIDATION } = require("./error_codes.cjs");
/**
* Create and configure the MCP server with tools
diff --git a/actions/setup/js/safe_output_handler_manager.cjs b/actions/setup/js/safe_output_handler_manager.cjs
index fb29aaa534..5f7a2845f7 100644
--- a/actions/setup/js/safe_output_handler_manager.cjs
+++ b/actions/setup/js/safe_output_handler_manager.cjs
@@ -11,6 +11,7 @@
const { loadAgentOutput } = require("./load_agent_output.cjs");
const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_CONFIG, ERR_PARSE, ERR_VALIDATION } = require("./error_codes.cjs");
const { hasUnresolvedTemporaryIds, replaceTemporaryIdReferences, normalizeTemporaryId } = require("./temporary_id.cjs");
const { generateMissingInfoSections } = require("./missing_info_formatter.cjs");
const { setCollectedMissings } = require("./missing_messages_helper.cjs");
@@ -85,7 +86,7 @@ const CODE_PUSH_TYPES = new Set(["push_to_pull_request_branch", "create_pull_req
*/
function loadConfig() {
if (!process.env.GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG) {
- throw new Error("GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG environment variable is required but not set");
+ throw new Error(`${ERR_CONFIG}: GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG environment variable is required but not set`);
}
try {
@@ -94,7 +95,7 @@ function loadConfig() {
// Normalize config keys: convert hyphens to underscores
return Object.fromEntries(Object.entries(config).map(([k, v]) => [k.replace(/-/g, "_"), v]));
} catch (error) {
- throw new Error(`Failed to parse GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: ${getErrorMessage(error)}`);
+ throw new Error(`${ERR_PARSE}: Failed to parse GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: ${getErrorMessage(error)}`);
}
}
@@ -990,7 +991,7 @@ async function main() {
core.info("Safe Output Handler Manager completed");
} catch (error) {
- core.setFailed(`Handler manager failed: ${getErrorMessage(error)}`);
+ core.setFailed(`${ERR_VALIDATION}: Handler manager failed: ${getErrorMessage(error)}`);
} finally {
// Guarantee the manifest file exists for artifact upload even when the handler fails.
// This is a no-op if the file was already created by createManifestLogger().
diff --git a/actions/setup/js/safe_output_manifest.cjs b/actions/setup/js/safe_output_manifest.cjs
index adc2ecda61..8cb1b316ea 100644
--- a/actions/setup/js/safe_output_manifest.cjs
+++ b/actions/setup/js/safe_output_manifest.cjs
@@ -2,6 +2,7 @@
const fs = require("fs");
const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_SYSTEM } = require("./error_codes.cjs");
/**
* Default path for the safe output items manifest file.
@@ -75,7 +76,7 @@ function createManifestLogger(manifestFile = MANIFEST_FILE_PATH) {
try {
fs.appendFileSync(manifestFile, jsonLine);
} catch (error) {
- throw new Error(`Failed to write to manifest file: ${getErrorMessage(error)}`);
+ throw new Error(`${ERR_SYSTEM}: Failed to write to manifest file: ${getErrorMessage(error)}`);
}
};
}
@@ -92,7 +93,7 @@ function ensureManifestExists(manifestFile = MANIFEST_FILE_PATH) {
try {
fs.writeFileSync(manifestFile, "");
} catch (error) {
- throw new Error(`Failed to create manifest file: ${getErrorMessage(error)}`);
+ throw new Error(`${ERR_SYSTEM}: Failed to create manifest file: ${getErrorMessage(error)}`);
}
}
}
diff --git a/actions/setup/js/safe_output_processor.cjs b/actions/setup/js/safe_output_processor.cjs
index e0aa6a7129..328bee0449 100644
--- a/actions/setup/js/safe_output_processor.cjs
+++ b/actions/setup/js/safe_output_processor.cjs
@@ -10,6 +10,7 @@ const { loadAgentOutput } = require("./load_agent_output.cjs");
const { generateStagedPreview } = require("./staged_preview.cjs");
const { parseAllowedItems, resolveTarget } = require("./safe_output_helpers.cjs");
const { getSafeOutputConfig, validateMaxCount } = require("./safe_output_validator.cjs");
+const { ERR_VALIDATION } = require("./error_codes.cjs");
/**
* @typedef {Object} ProcessorConfig
diff --git a/actions/setup/js/safe_output_unified_handler_manager.cjs b/actions/setup/js/safe_output_unified_handler_manager.cjs
index 79dd6a65ea..3283317272 100644
--- a/actions/setup/js/safe_output_unified_handler_manager.cjs
+++ b/actions/setup/js/safe_output_unified_handler_manager.cjs
@@ -16,6 +16,7 @@
const { loadAgentOutput } = require("./load_agent_output.cjs");
const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_CONFIG, ERR_PARSE, ERR_VALIDATION } = require("./error_codes.cjs");
const { hasUnresolvedTemporaryIds, replaceTemporaryIdReferences, normalizeTemporaryId, loadTemporaryIdMap, isTemporaryId } = require("./temporary_id.cjs");
const { generateMissingInfoSections } = require("./missing_info_formatter.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");
@@ -127,7 +128,7 @@ function loadConfig() {
}
}
} catch (error) {
- throw new Error(`Failed to parse GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: ${getErrorMessage(error)}`);
+ throw new Error(`${ERR_PARSE}: Failed to parse GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: ${getErrorMessage(error)}`);
}
}
@@ -140,13 +141,13 @@ function loadConfig() {
// Explicitly provided project config takes precedence over auto-split config
Object.assign(project, Object.fromEntries(Object.entries(config).map(([k, v]) => [k.replace(/-/g, "_"), v])));
} catch (error) {
- throw new Error(`Failed to parse GH_AW_SAFE_OUTPUTS_PROJECT_HANDLER_CONFIG: ${getErrorMessage(error)}`);
+ throw new Error(`${ERR_PARSE}: Failed to parse GH_AW_SAFE_OUTPUTS_PROJECT_HANDLER_CONFIG: ${getErrorMessage(error)}`);
}
}
// At least one config must be present
if (Object.keys(regular).length === 0 && Object.keys(project).length === 0) {
- throw new Error("At least one of GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG or GH_AW_SAFE_OUTPUTS_PROJECT_HANDLER_CONFIG environment variables is required");
+ throw new Error(`${ERR_CONFIG}: At least one of GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG or GH_AW_SAFE_OUTPUTS_PROJECT_HANDLER_CONFIG environment variables is required`);
}
const regularCount = Object.keys(regular).length;
@@ -168,7 +169,7 @@ function loadConfig() {
async function setupProjectGitHubClient() {
const projectToken = process.env.GH_AW_PROJECT_GITHUB_TOKEN;
if (!projectToken) {
- throw new Error("GH_AW_PROJECT_GITHUB_TOKEN environment variable is required for project-related safe outputs. " + "Configure a GitHub token with Projects permissions in your workflow secrets.");
+ throw new Error(`${ERR_CONFIG}: GH_AW_PROJECT_GITHUB_TOKEN environment variable is required for project-related safe outputs. Configure a GitHub token with Projects permissions in your workflow secrets.`);
}
core.info("Setting up separate Octokit client for project handlers with GH_AW_PROJECT_GITHUB_TOKEN");
@@ -242,7 +243,7 @@ async function loadHandlers(configs, projectOctokit = null, prReviewBuffer = nul
try {
// Ensure we have an Octokit instance for project handlers
if (!projectOctokit) {
- throw new Error(`Octokit instance is required for project handler ${type}. This is a configuration error - projectOctokit should be provided when project handlers are configured.`);
+ throw new Error(`${ERR_CONFIG}: Octokit instance is required for project handler ${type}. This is a configuration error - projectOctokit should be provided when project handlers are configured.`);
}
const handlerModule = require(handlerPath);
@@ -749,7 +750,7 @@ function normalizeAndValidateTemporaryId(message, messageType, messageIndex) {
}
if (typeof message.temporary_id !== "string") {
- throw new Error(`Message ${messageIndex + 1} (${messageType}): temporary_id must be a string (got ${typeof message.temporary_id})`);
+ throw new Error(`${ERR_VALIDATION}: Message ${messageIndex + 1} (${messageType}): temporary_id must be a string (got ${typeof message.temporary_id})`);
}
const raw = message.temporary_id;
@@ -757,7 +758,7 @@ function normalizeAndValidateTemporaryId(message, messageType, messageIndex) {
const withoutHash = trimmed.startsWith("#") ? trimmed.substring(1).trim() : trimmed;
if (!isTemporaryId(withoutHash)) {
- throw new Error(`Message ${messageIndex + 1} (${messageType}): invalid temporary_id '${raw}'. Temporary IDs must be 'aw_' followed by 3 to 8 alphanumeric characters (A-Za-z0-9), e.g. 'aw_abc' or 'aw_Test123'`);
+ throw new Error(`${ERR_VALIDATION}: Message ${messageIndex + 1} (${messageType}): invalid temporary_id '${raw}'. Temporary IDs must be 'aw_' followed by 3 to 8 alphanumeric characters (A-Za-z0-9), e.g. 'aw_abc' or 'aw_Test123'`);
}
// Normalize to the strict bare ID to keep lookups consistent.
@@ -1157,7 +1158,7 @@ async function main() {
core.info("=== Unified Safe Output Handler Manager Completed ===");
} catch (error) {
- core.setFailed(`Handler manager failed: ${getErrorMessage(error)}`);
+ core.setFailed(`${ERR_VALIDATION}: Handler manager failed: ${getErrorMessage(error)}`);
} finally {
// Guarantee the manifest file exists for artifact upload even when the handler fails.
// This is a no-op if the file was already created by createManifestLogger().
diff --git a/actions/setup/js/safe_outputs_append.cjs b/actions/setup/js/safe_outputs_append.cjs
index 88bb82dcd9..1bb25db55f 100644
--- a/actions/setup/js/safe_outputs_append.cjs
+++ b/actions/setup/js/safe_outputs_append.cjs
@@ -3,6 +3,7 @@
const { getErrorMessage } = require("./error_helpers.cjs");
const fs = require("fs");
+const { ERR_SYSTEM, ERR_VALIDATION } = require("./error_codes.cjs");
/**
* Create an append function for the safe outputs file
@@ -20,7 +21,7 @@ function createAppendFunction(outputFile) {
* @param {Object} entry - The entry to append
*/
return function appendSafeOutput(entry) {
- if (!outputFile) throw new Error("No output file configured");
+ if (!outputFile) throw new Error(`${ERR_VALIDATION}: No output file configured`);
// Normalize type to use underscores (convert any dashes to underscores)
entry.type = entry.type.replace(/-/g, "_");
// CRITICAL: Use JSON.stringify WITHOUT formatting parameters for JSONL format
@@ -29,7 +30,7 @@ function createAppendFunction(outputFile) {
try {
fs.appendFileSync(outputFile, jsonLine);
} catch (error) {
- throw new Error(`Failed to write to output file: ${getErrorMessage(error)}`);
+ throw new Error(`${ERR_SYSTEM}: Failed to write to output file: ${getErrorMessage(error)}`);
}
};
}
diff --git a/actions/setup/js/safe_outputs_handlers.cjs b/actions/setup/js/safe_outputs_handlers.cjs
index 4e41efdbaa..5880d09c14 100644
--- a/actions/setup/js/safe_outputs_handlers.cjs
+++ b/actions/setup/js/safe_outputs_handlers.cjs
@@ -12,6 +12,7 @@ const { getBaseBranch } = require("./get_base_branch.cjs");
const { generateGitPatch } = require("./generate_git_patch.cjs");
const { enforceCommentLimits } = require("./comment_limit_helpers.cjs");
const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_CONFIG, ERR_SYSTEM, ERR_VALIDATION } = require("./error_codes.cjs");
/**
* Create handlers for safe output tools
@@ -84,7 +85,7 @@ function createHandlers(server, appendSafeOutput, config = {}) {
*/
const uploadAssetHandler = args => {
const branchName = process.env.GH_AW_ASSETS_BRANCH;
- if (!branchName) throw new Error("GH_AW_ASSETS_BRANCH not set");
+ if (!branchName) throw new Error(`${ERR_CONFIG}: GH_AW_ASSETS_BRANCH not set`);
// Normalize the branch name to ensure it's a valid git branch name
const normalizedBranchName = normalizeBranchName(branchName);
@@ -100,12 +101,12 @@ function createHandlers(server, appendSafeOutput, config = {}) {
const isInTmp = absolutePath.startsWith(tmpDir);
if (!isInWorkspace && !isInTmp) {
- throw new Error(`File path must be within workspace directory (${workspaceDir}) or /tmp directory. ` + `Provided path: ${filePath} (resolved to: ${absolutePath})`);
+ throw new Error(`${ERR_CONFIG}: File path must be within workspace directory (${workspaceDir}) or /tmp directory. ` + `Provided path: ${filePath} (resolved to: ${absolutePath})`);
}
// Validate file exists
if (!fs.existsSync(filePath)) {
- throw new Error(`File not found: ${filePath}`);
+ throw new Error(`${ERR_SYSTEM}: File not found: ${filePath}`);
}
// Get file stats
@@ -116,7 +117,7 @@ function createHandlers(server, appendSafeOutput, config = {}) {
// Check file size - read from environment variable if available
const maxSizeKB = process.env.GH_AW_ASSETS_MAX_SIZE_KB ? parseInt(process.env.GH_AW_ASSETS_MAX_SIZE_KB, 10) : 10240; // Default 10MB
if (sizeKB > maxSizeKB) {
- throw new Error(`File size ${sizeKB} KB exceeds maximum allowed size ${maxSizeKB} KB`);
+ throw new Error(`${ERR_VALIDATION}: File size ${sizeKB} KB exceeds maximum allowed size ${maxSizeKB} KB`);
}
// Check file extension - read from environment variable if available
@@ -131,7 +132,7 @@ function createHandlers(server, appendSafeOutput, config = {}) {
];
if (!allowedExts.includes(ext)) {
- throw new Error(`File extension '${ext}' is not allowed. Allowed extensions: ${allowedExts.join(", ")}`);
+ throw new Error(`${ERR_VALIDATION}: File extension '${ext}' is not allowed. Allowed extensions: ${allowedExts.join(", ")}`);
}
// Create assets directory
diff --git a/actions/setup/js/safe_outputs_mcp_server.cjs b/actions/setup/js/safe_outputs_mcp_server.cjs
index d2900d2722..48c390ee7e 100644
--- a/actions/setup/js/safe_outputs_mcp_server.cjs
+++ b/actions/setup/js/safe_outputs_mcp_server.cjs
@@ -18,6 +18,7 @@ const { createHandlers } = require("./safe_outputs_handlers.cjs");
const { attachHandlers, registerPredefinedTools, registerDynamicTools } = require("./safe_outputs_tools_loader.cjs");
const { bootstrapSafeOutputsServer, cleanupConfigFile } = require("./safe_outputs_bootstrap.cjs");
const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_VALIDATION } = require("./error_codes.cjs");
/**
* Start the safe-outputs MCP server
@@ -56,7 +57,7 @@ function startSafeOutputsServer(options = {}) {
registerDynamicTools(server, toolsWithHandlers, safeOutputsConfig, outputFile, registerTool, normalizeTool);
server.debug(` tools: ${Object.keys(server.tools).join(", ")}`);
- if (!Object.keys(server.tools).length) throw new Error("No tools enabled in configuration");
+ if (!Object.keys(server.tools).length) throw new Error(`${ERR_VALIDATION}: No tools enabled in configuration`);
// Note: We do NOT cleanup the config file here because it's needed by the ingestion
// phase (collect_ndjson_output.cjs) that runs after the MCP server completes.
diff --git a/actions/setup/js/setup_threat_detection.cjs b/actions/setup/js/setup_threat_detection.cjs
index 4a8089d3ac..becd292059 100644
--- a/actions/setup/js/setup_threat_detection.cjs
+++ b/actions/setup/js/setup_threat_detection.cjs
@@ -16,6 +16,7 @@ const fs = require("fs");
const path = require("path");
const { checkFileExists } = require("./file_helpers.cjs");
const { AGENT_OUTPUT_FILENAME } = require("./constants.cjs");
+const { ERR_VALIDATION } = require("./error_codes.cjs");
/**
* Main entry point for setting up threat detection
@@ -26,7 +27,7 @@ async function main() {
// At runtime, markdown files are copied to /opt/gh-aw/prompts/ by the setup action
const templatePath = "/opt/gh-aw/prompts/threat_detection.md";
if (!fs.existsSync(templatePath)) {
- core.setFailed(`Threat detection template not found at: ${templatePath}`);
+ core.setFailed(`${ERR_VALIDATION}: Threat detection template not found at: ${templatePath}`);
return;
}
const templateContent = fs.readFileSync(templatePath, "utf-8");
@@ -66,7 +67,7 @@ async function main() {
}
if (patchFiles.length === 0 && hasPatch) {
- core.setFailed(`Patch file(s) expected but not found in: ${threatDetectionDir}`);
+ core.setFailed(`${ERR_VALIDATION}: Patch file(s) expected but not found in: ${threatDetectionDir}`);
return;
}
diff --git a/actions/setup/js/staged_preview.cjs b/actions/setup/js/staged_preview.cjs
index ecae209208..e9c10df4fe 100644
--- a/actions/setup/js/staged_preview.cjs
+++ b/actions/setup/js/staged_preview.cjs
@@ -11,6 +11,7 @@
* @param {(item: any, index: number) => string} options.renderItem - Function to render each item as markdown
* @returns {Promise}
*/
+const { ERR_SYSTEM } = require("./error_codes.cjs");
async function generateStagedPreview(options) {
const { title, description, items, renderItem } = options;
@@ -28,7 +29,7 @@ async function generateStagedPreview(options) {
core.info(summaryContent);
core.info(`📝 ${title} preview written to step summary`);
} catch (error) {
- core.setFailed(error instanceof Error ? error : String(error));
+ core.setFailed(`${ERR_SYSTEM}: ${error instanceof Error ? error.message : String(error)}`);
}
}
diff --git a/actions/setup/js/substitute_placeholders.cjs b/actions/setup/js/substitute_placeholders.cjs
index 2cb955d2c9..42dbef95f5 100644
--- a/actions/setup/js/substitute_placeholders.cjs
+++ b/actions/setup/js/substitute_placeholders.cjs
@@ -1,5 +1,6 @@
const fs = require("fs");
const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_SYSTEM } = require("./error_codes.cjs");
const substitutePlaceholders = async ({ file, substitutions }) => {
if (typeof core !== "undefined") {
@@ -46,7 +47,7 @@ const substitutePlaceholders = async ({ file, substitutions }) => {
if (typeof core !== "undefined") {
core.info(`[substitutePlaceholders] ERROR reading file: ${errorMessage}`);
}
- throw new Error(`Failed to read file ${file}: ${errorMessage}`);
+ throw new Error(`${ERR_SYSTEM}: Failed to read file ${file}: ${errorMessage}`);
}
// Perform substitutions
@@ -108,7 +109,7 @@ const substitutePlaceholders = async ({ file, substitutions }) => {
if (typeof core !== "undefined") {
core.info(`[substitutePlaceholders] ERROR writing file: ${errorMessage}`);
}
- throw new Error(`Failed to write file ${file}: ${errorMessage}`);
+ throw new Error(`${ERR_SYSTEM}: Failed to write file ${file}: ${errorMessage}`);
}
return `Successfully substituted ${Object.keys(substitutions).length} placeholder(s) in ${file}`;
diff --git a/actions/setup/js/unlock-issue.cjs b/actions/setup/js/unlock-issue.cjs
index 44924b48ea..8732765417 100644
--- a/actions/setup/js/unlock-issue.cjs
+++ b/actions/setup/js/unlock-issue.cjs
@@ -8,6 +8,7 @@
*/
const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_NOT_FOUND } = require("./error_codes.cjs");
async function main() {
// Log actor and event information for debugging
@@ -17,7 +18,7 @@ async function main() {
const issueNumber = context.issue.number;
if (!issueNumber) {
- core.setFailed("Issue number not found in context");
+ core.setFailed(`${ERR_NOT_FOUND}: Issue number not found in context`);
return;
}
@@ -59,7 +60,7 @@ async function main() {
} catch (error) {
const errorMessage = getErrorMessage(error);
core.error(`Failed to unlock issue: ${errorMessage}`);
- core.setFailed(`Failed to unlock issue #${issueNumber}: ${errorMessage}`);
+ core.setFailed(`${ERR_NOT_FOUND}: Failed to unlock issue #${issueNumber}: ${errorMessage}`);
}
}
diff --git a/actions/setup/js/unlock-issue.test.cjs b/actions/setup/js/unlock-issue.test.cjs
index 6a2540f2cc..fc6f995e70 100644
--- a/actions/setup/js/unlock-issue.test.cjs
+++ b/actions/setup/js/unlock-issue.test.cjs
@@ -1,6 +1,7 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
import fs from "fs";
import path from "path";
+const { ERR_NOT_FOUND } = require("./error_codes.cjs");
const mockCore = { debug: vi.fn(), info: vi.fn(), warning: vi.fn(), error: vi.fn(), setFailed: vi.fn(), setOutput: vi.fn() },
mockGithub = { rest: { issues: { get: vi.fn(), unlock: vi.fn() } } },
mockContext = { eventName: "issues", runId: 12345, repo: { owner: "testowner", repo: "testrepo" }, issue: { number: 42 }, payload: { issue: { number: 42 }, repository: { html_url: "https://github.com/testowner/testrepo" } } };
@@ -48,7 +49,7 @@ const mockCore = { debug: vi.fn(), info: vi.fn(), warning: vi.fn(), error: vi.fn
delete global.context.payload.issue,
await eval(`(async () => { ${unlockIssueScript}; await main(); })()`),
expect(mockGithub.rest.issues.unlock).not.toHaveBeenCalled(),
- expect(mockCore.setFailed).toHaveBeenCalledWith("Issue number not found in context"));
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_NOT_FOUND}: Issue number not found in context`));
}),
it("should handle API errors gracefully", async () => {
mockGithub.rest.issues.get.mockResolvedValue({ data: { number: 42, locked: !0 } });
@@ -57,14 +58,14 @@ const mockCore = { debug: vi.fn(), info: vi.fn(), warning: vi.fn(), error: vi.fn
await eval(`(async () => { ${unlockIssueScript}; await main(); })()`),
expect(mockGithub.rest.issues.unlock).toHaveBeenCalled(),
expect(mockCore.error).toHaveBeenCalledWith("Failed to unlock issue: Issue was not locked"),
- expect(mockCore.setFailed).toHaveBeenCalledWith("Failed to unlock issue #42: Issue was not locked"));
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_NOT_FOUND}: Failed to unlock issue #42: Issue was not locked`));
}),
it("should handle non-Error exceptions", async () => {
(mockGithub.rest.issues.get.mockResolvedValue({ data: { number: 42, locked: !0 } }),
mockGithub.rest.issues.unlock.mockRejectedValue("String error"),
await eval(`(async () => { ${unlockIssueScript}; await main(); })()`),
expect(mockCore.error).toHaveBeenCalledWith("Failed to unlock issue: String error"),
- expect(mockCore.setFailed).toHaveBeenCalledWith("Failed to unlock issue #42: String error"));
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_NOT_FOUND}: Failed to unlock issue #42: String error`));
}),
it("should work with different issue numbers", async () => {
((global.context.issue = { number: 200 }),
@@ -84,7 +85,7 @@ const mockCore = { debug: vi.fn(), info: vi.fn(), warning: vi.fn(), error: vi.fn
await eval(`(async () => { ${unlockIssueScript}; await main(); })()`),
expect(mockGithub.rest.issues.unlock).toHaveBeenCalled(),
expect(mockCore.error).toHaveBeenCalledWith("Failed to unlock issue: Resource not accessible by integration"),
- expect(mockCore.setFailed).toHaveBeenCalledWith("Failed to unlock issue #42: Resource not accessible by integration"));
+ expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_NOT_FOUND}: Failed to unlock issue #42: Resource not accessible by integration`));
}),
it("should skip if issue is already unlocked (redundant test for completeness)", async () => {
(mockGithub.rest.issues.get.mockResolvedValue({ data: { number: 42, locked: !1 } }),
diff --git a/actions/setup/js/update_discussion.cjs b/actions/setup/js/update_discussion.cjs
index f9ea5e744a..d7463339c8 100644
--- a/actions/setup/js/update_discussion.cjs
+++ b/actions/setup/js/update_discussion.cjs
@@ -8,6 +8,7 @@
const { isDiscussionContext, getDiscussionNumber } = require("./update_context_helpers.cjs");
const { createUpdateHandlerFactory, createStandardFormatResult } = require("./update_handler_factory.cjs");
const { sanitizeTitle } = require("./sanitize_title.cjs");
+const { ERR_NOT_FOUND } = require("./error_codes.cjs");
/**
* Execute the discussion update API call using GraphQL
@@ -40,7 +41,7 @@ async function executeDiscussionUpdate(github, context, discussionNumber, update
const discussion = queryResult?.repository?.discussion;
if (!discussion) {
- throw new Error(`Discussion #${discussionNumber} not found`);
+ throw new Error(`${ERR_NOT_FOUND}: Discussion #${discussionNumber} not found`);
}
// Build mutation for updating discussion
diff --git a/actions/setup/js/update_issue.cjs b/actions/setup/js/update_issue.cjs
index fa1550aa63..6208744d96 100644
--- a/actions/setup/js/update_issue.cjs
+++ b/actions/setup/js/update_issue.cjs
@@ -14,6 +14,7 @@ const { updateBody } = require("./update_pr_description_helpers.cjs");
const { loadTemporaryProjectMap, replaceTemporaryProjectReferences } = require("./temporary_id.cjs");
const { sanitizeTitle } = require("./sanitize_title.cjs");
const { tryEnforceArrayLimit } = require("./limit_enforcement_helpers.cjs");
+const { ERR_VALIDATION } = require("./error_codes.cjs");
/**
* Maximum limits for issue update parameters to prevent resource exhaustion.
@@ -56,7 +57,7 @@ async function executeIssueUpdate(github, context, issueNumber, updateData) {
if (titlePrefix) {
const currentTitle = currentIssue.title || "";
if (!currentTitle.startsWith(titlePrefix)) {
- throw new Error(`Issue title "${currentTitle}" does not start with required prefix "${titlePrefix}"`);
+ throw new Error(`${ERR_VALIDATION}: Issue title "${currentTitle}" does not start with required prefix "${titlePrefix}"`);
}
core.info(`✓ Title prefix validation passed: "${titlePrefix}"`);
}
diff --git a/actions/setup/js/update_project.cjs b/actions/setup/js/update_project.cjs
index b874cdcfca..b176d9e17f 100644
--- a/actions/setup/js/update_project.cjs
+++ b/actions/setup/js/update_project.cjs
@@ -5,6 +5,7 @@ const { loadAgentOutput } = require("./load_agent_output.cjs");
const { getErrorMessage } = require("./error_helpers.cjs");
const { loadTemporaryIdMapFromResolved, resolveIssueNumber, isTemporaryId, normalizeTemporaryId } = require("./temporary_id.cjs");
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
+const { ERR_API, ERR_CONFIG, ERR_NOT_FOUND, ERR_PARSE, ERR_VALIDATION } = require("./error_codes.cjs");
/**
* Normalize agent output keys for update_project.
@@ -76,12 +77,12 @@ function logGraphQLError(error, operation) {
*/
function parseProjectInput(projectUrl) {
if (!projectUrl || typeof projectUrl !== "string") {
- throw new Error(`Invalid project input: expected string, got ${typeof projectUrl}. The "project" field is required and must be a full GitHub project URL.`);
+ throw new Error(`${ERR_VALIDATION}: Invalid project input: expected string, got ${typeof projectUrl}. The "project" field is required and must be a full GitHub project URL.`);
}
const urlMatch = projectUrl.match(/^https:\/\/[^/]+\/(?:users|orgs)\/[^/]+\/projects\/(\d+)/);
if (!urlMatch) {
- throw new Error(`Invalid project URL: "${projectUrl}". The "project" field must be a full GitHub project URL (e.g., https://github.com/orgs/myorg/projects/123).`);
+ throw new Error(`${ERR_VALIDATION}: Invalid project URL: "${projectUrl}". The "project" field must be a full GitHub project URL (e.g., https://github.com/orgs/myorg/projects/123).`);
}
return urlMatch[1];
@@ -94,12 +95,12 @@ function parseProjectInput(projectUrl) {
*/
function parseProjectUrl(projectUrl) {
if (!projectUrl || typeof projectUrl !== "string") {
- throw new Error(`Invalid project input: expected string, got ${typeof projectUrl}. The "project" field is required and must be a full GitHub project URL.`);
+ throw new Error(`${ERR_VALIDATION}: Invalid project input: expected string, got ${typeof projectUrl}. The "project" field is required and must be a full GitHub project URL.`);
}
const match = projectUrl.match(/^https:\/\/[^/]+\/(users|orgs)\/([^/]+)\/projects\/(\d+)/);
if (!match) {
- throw new Error(`Invalid project URL: "${projectUrl}". The "project" field must be a full GitHub project URL (e.g., https://github.com/orgs/myorg/projects/123).`);
+ throw new Error(`${ERR_VALIDATION}: Invalid project URL: "${projectUrl}". The "project" field must be a full GitHub project URL (e.g., https://github.com/orgs/myorg/projects/123).`);
}
return {
@@ -260,7 +261,9 @@ async function resolveProjectV2(projectInfo, projectNumberInt, github) {
} catch (fallbackError) {
// Both direct query and fallback list query failed - this could be a transient API error
const who = projectInfo.scope === "orgs" ? `org ${projectInfo.ownerLogin}` : `user ${projectInfo.ownerLogin}`;
- throw new Error(`Unable to resolve project #${projectNumberInt} for ${who}. Both direct projectV2 query and fallback projectsV2 list query failed. This may be a transient GitHub API error. Error: ${getErrorMessage(fallbackError)}`);
+ throw new Error(
+ `${ERR_NOT_FOUND}: Unable to resolve project #${projectNumberInt} for ${who}. Both direct projectV2 query and fallback projectsV2 list query failed. This may be a transient GitHub API error. Error: ${getErrorMessage(fallbackError)}`
+ );
}
const nodes = Array.isArray(list.nodes) ? list.nodes : [];
@@ -272,7 +275,7 @@ async function resolveProjectV2(projectInfo, projectNumberInt, github) {
const total = typeof list.totalCount === "number" ? ` (totalCount=${list.totalCount})` : "";
const who = projectInfo.scope === "orgs" ? `org ${projectInfo.ownerLogin}` : `user ${projectInfo.ownerLogin}`;
- throw new Error(`Project #${projectNumberInt} not found or not accessible for ${who}.${total} Accessible Projects v2: ${summary}`);
+ throw new Error(`${ERR_NOT_FOUND}: Project #${projectNumberInt} not found or not accessible for ${who}.${total} Accessible Projects v2: ${summary}`);
}
/**
* Check if a field name conflicts with unsupported GitHub built-in field types
@@ -399,7 +402,7 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient =
// @ts-ignore - global.github is set by setupGlobals() from github-script context
const github = githubClient || global.github;
if (!github) {
- throw new Error("GitHub client is required but not provided. Either pass a github client to updateProject() or ensure global.github is set.");
+ throw new Error(`${ERR_CONFIG}: GitHub client is required but not provided. Either pass a github client to updateProject() or ensure global.github is set.`);
}
const { owner, repo } = context.repo;
const projectInfo = parseProjectUrl(output.project);
@@ -479,7 +482,7 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient =
try {
const projectNumberInt = parseInt(projectNumberFromUrl, 10);
if (!Number.isFinite(projectNumberInt)) {
- throw new Error(`Invalid project number parsed from URL: ${projectNumberFromUrl}`);
+ throw new Error(`${ERR_PARSE}: Invalid project number parsed from URL: ${projectNumberFromUrl}`);
}
const project = await resolveProjectV2(projectInfo, projectNumberInt, github);
projectId = project.id;
@@ -495,17 +498,17 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient =
if (wantsCreateView) {
const view = output?.view;
if (!view || typeof view !== "object") {
- throw new Error('Invalid view. When operation is "create_view", you must provide view: { name, layout, ... }.');
+ throw new Error(`${ERR_VALIDATION}: Invalid view. When operation is "create_view", you must provide view: { name, layout, ... }.`);
}
const name = typeof view.name === "string" ? view.name.trim() : "";
if (!name) {
- throw new Error('Invalid view.name. When operation is "create_view", view.name is required and must be a non-empty string.');
+ throw new Error(`${ERR_VALIDATION}: Invalid view.name. When operation is "create_view", view.name is required and must be a non-empty string.`);
}
const layout = typeof view.layout === "string" ? view.layout.trim() : "";
if (!layout || !["table", "board", "roadmap"].includes(layout)) {
- throw new Error("Invalid view.layout. Must be one of: table, board, roadmap.");
+ throw new Error(`${ERR_VALIDATION}: Invalid view.layout. Must be one of: table, board, roadmap.`);
}
const filter = typeof view.filter === "string" ? view.filter : undefined;
@@ -514,7 +517,7 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient =
if (visibleFields) {
const invalid = visibleFields.filter(v => typeof v !== "number" || !Number.isFinite(v));
if (invalid.length > 0) {
- throw new Error(`Invalid view.visible_fields. Must be an array of numbers (field IDs). Invalid values: ${invalid.map(v => JSON.stringify(v)).join(", ")}`);
+ throw new Error(`${ERR_VALIDATION}: Invalid view.visible_fields. Must be an array of numbers (field IDs). Invalid values: ${invalid.map(v => JSON.stringify(v)).join(", ")}`);
}
}
@@ -528,7 +531,7 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient =
}
if (typeof github.request !== "function") {
- throw new Error("GitHub client does not support github.request(); cannot call Projects Views REST API.");
+ throw new Error(`${ERR_API}: GitHub client does not support github.request(); cannot call Projects Views REST API.`);
}
const route = projectInfo.scope === "orgs" ? "POST /orgs/{org}/projectsV2/{project_number}/views" : "POST /users/{user_id}/projectsV2/{project_number}/views";
@@ -565,7 +568,7 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient =
if (wantsCreateFields) {
const fieldsConfig = output?.field_definitions;
if (!fieldsConfig || !Array.isArray(fieldsConfig)) {
- throw new Error('Invalid field_definitions. When operation is "create_fields", you must provide field_definitions as an array.');
+ throw new Error(`${ERR_VALIDATION}: Invalid field_definitions. When operation is "create_fields", you must provide field_definitions as an array.`);
}
core.info(`[3/4] Creating ${fieldsConfig.length} project field(s)...`);
@@ -700,11 +703,11 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient =
// Validate IDs used for draft chaining.
// Draft issue chaining must use strict temporary IDs to match the unified handler manager.
if (temporaryId && !isTemporaryId(temporaryId)) {
- throw new Error(`Invalid temporary_id format: "${temporaryId}". Expected format: aw_ followed by 3 to 8 alphanumeric characters (e.g., "aw_abc", "aw_Test123").`);
+ throw new Error(`${ERR_VALIDATION}: Invalid temporary_id format: "${temporaryId}". Expected format: aw_ followed by 3 to 8 alphanumeric characters (e.g., "aw_abc", "aw_Test123").`);
}
if (draftIssueId && !isTemporaryId(draftIssueId)) {
- throw new Error(`Invalid draft_issue_id format: "${draftIssueId}". Expected format: aw_ followed by 3 to 8 alphanumeric characters (e.g., "aw_abc", "aw_Test123").`);
+ throw new Error(`${ERR_VALIDATION}: Invalid draft_issue_id format: "${draftIssueId}". Expected format: aw_ followed by 3 to 8 alphanumeric characters (e.g., "aw_abc", "aw_Test123").`);
}
const draftTitle = typeof output.draft_title === "string" ? output.draft_title.trim() : "";
@@ -734,17 +737,17 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient =
itemId = existingDraftItem.id;
core.info(`✓ Found draft issue "${draftTitle}" by title fallback`);
} else {
- throw new Error(`draft_issue_id "${draftIssueId}" not found in temporary ID map and no draft with title "${draftTitle}" found`);
+ throw new Error(`${ERR_NOT_FOUND}: draft_issue_id "${draftIssueId}" not found in temporary ID map and no draft with title "${draftTitle}" found`);
}
} else {
- throw new Error(`draft_issue_id "${draftIssueId}" not found in temporary ID map and no draft_title provided for fallback lookup`);
+ throw new Error(`${ERR_NOT_FOUND}: draft_issue_id "${draftIssueId}" not found in temporary ID map and no draft_title provided for fallback lookup`);
}
}
}
// Mode 2: Create new draft or find by title
else {
if (!draftTitle) {
- throw new Error('Invalid draft_title. When content_type is "draft_issue" and draft_issue_id is not provided, draft_title is required and must be a non-empty string.');
+ throw new Error(`${ERR_CONFIG}: Invalid draft_title. When content_type is "draft_issue" and draft_issue_id is not provided, draft_title is required and must be a non-empty string.`);
}
// Check for existing draft issue with the same title
@@ -929,14 +932,14 @@ async function updateProject(output, temporaryIdMap = new Map(), githubClient =
if (resolved.wasTemporaryId) {
if (resolved.errorMessage || !resolved.resolved) {
- throw new Error(`Failed to resolve temporary ID in content_number: ${resolved.errorMessage || "Unknown error"}`);
+ throw new Error(`${ERR_API}: Failed to resolve temporary ID in content_number: ${resolved.errorMessage || "Unknown error"}`);
}
core.info(`✓ Resolved temporary ID ${sanitizedContentNumber} to issue #${resolved.resolved.number}`);
contentNumber = resolved.resolved.number;
} else {
// Not a temporary ID - validate as numeric
if (!/^\d+$/.test(sanitizedContentNumber)) {
- throw new Error(`Invalid content number "${rawContentNumber}". Provide a positive integer or a valid temporary ID (format: aw_ followed by 3-8 alphanumeric characters).`);
+ throw new Error(`${ERR_VALIDATION}: Invalid content number "${rawContentNumber}". Provide a positive integer or a valid temporary ID (format: aw_ followed by 3-8 alphanumeric characters).`);
}
contentNumber = Number.parseInt(sanitizedContentNumber, 10);
}
@@ -1142,7 +1145,7 @@ async function main(config = {}, githubClient = null) {
const github = githubClient || global.github;
if (!github) {
- throw new Error("GitHub client is required but not provided. Either pass a github client to main() or ensure global.github is set by github-script action.");
+ throw new Error(`${ERR_CONFIG}: GitHub client is required but not provided. Either pass a github client to main() or ensure global.github is set by github-script action.`);
}
// Extract configuration
@@ -1237,7 +1240,7 @@ async function main(config = {}, githubClient = null) {
core.info(`Resolved temporary project ID ${projectStr} to ${resolved.projectUrl}`);
effectiveProjectUrl = resolved.projectUrl;
} else {
- throw new Error(`Temporary project ID '${projectStr}' not found. Ensure create_project was called before update_project.`);
+ throw new Error(`${ERR_NOT_FOUND}: Temporary project ID '${projectStr}' not found. Ensure create_project was called before update_project.`);
}
}
}
diff --git a/actions/setup/js/update_release.cjs b/actions/setup/js/update_release.cjs
index 6fb48814c8..d69a0dd806 100644
--- a/actions/setup/js/update_release.cjs
+++ b/actions/setup/js/update_release.cjs
@@ -11,6 +11,7 @@
const { getErrorMessage } = require("./error_helpers.cjs");
const { updateBody } = require("./update_pr_description_helpers.cjs");
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
+const { ERR_API, ERR_CONFIG, ERR_VALIDATION } = require("./error_codes.cjs");
// Content sanitization: message.body is sanitized by updateBody() helper
/**
@@ -76,7 +77,7 @@ async function main(config = {}) {
}
if (!releaseTag) {
- throw new Error("Release tag is required but not provided and cannot be inferred from event context");
+ throw new Error(`${ERR_CONFIG}: Release tag is required but not provided and cannot be inferred from event context`);
}
}
@@ -128,10 +129,10 @@ async function main(config = {}) {
// Check for specific error cases
if (errorMessage.includes("Not Found")) {
- throw new Error(`Release with tag '${tagInfo}' not found. Please ensure the tag exists.`);
+ throw new Error(`${ERR_VALIDATION}: Release with tag '${tagInfo}' not found. Please ensure the tag exists.`);
}
- throw new Error(`Failed to update release with tag ${tagInfo}: ${errorMessage}`);
+ throw new Error(`${ERR_API}: Failed to update release with tag ${tagInfo}: ${errorMessage}`);
}
};
}
diff --git a/actions/setup/js/upload_assets.cjs b/actions/setup/js/upload_assets.cjs
index e3f4936306..e32f89618f 100644
--- a/actions/setup/js/upload_assets.cjs
+++ b/actions/setup/js/upload_assets.cjs
@@ -6,6 +6,7 @@ const path = require("path");
const crypto = require("crypto");
const { loadAgentOutput } = require("./load_agent_output.cjs");
const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_API, ERR_CONFIG, ERR_SYSTEM, ERR_VALIDATION } = require("./error_codes.cjs");
/**
* Normalizes a branch name to be a valid git branch name.
@@ -62,7 +63,7 @@ async function main() {
// Get the branch name from environment variable (required)
const branchName = process.env.GH_AW_ASSETS_BRANCH;
if (!branchName || typeof branchName !== "string") {
- core.setFailed("GH_AW_ASSETS_BRANCH environment variable is required but not set");
+ core.setFailed(`${ERR_CONFIG}: GH_AW_ASSETS_BRANCH environment variable is required but not set`);
return;
}
@@ -121,14 +122,14 @@ async function main() {
const { fileName, sha, size, targetFileName } = asset;
if (!fileName || !sha || !targetFileName) {
- core.setFailed(`Invalid asset entry missing required fields: ${JSON.stringify(asset)}`);
+ core.setFailed(`${ERR_VALIDATION}: Invalid asset entry missing required fields: ${JSON.stringify(asset)}`);
return;
}
// Check if file exists in artifacts
const assetSourcePath = path.join("/tmp/gh-aw/safeoutputs/assets", fileName);
if (!fs.existsSync(assetSourcePath)) {
- core.setFailed(`Asset file not found: ${assetSourcePath}`);
+ core.setFailed(`${ERR_SYSTEM}: Asset file not found: ${assetSourcePath}`);
return;
}
@@ -137,7 +138,7 @@ async function main() {
const computedSha = crypto.createHash("sha256").update(fileContent).digest("hex");
if (computedSha !== sha) {
- core.setFailed(`SHA mismatch for ${fileName}: expected ${sha}, got ${computedSha}`);
+ core.setFailed(`${ERR_VALIDATION}: SHA mismatch for ${fileName}: expected ${sha}, got ${computedSha}`);
return;
}
@@ -159,7 +160,7 @@ async function main() {
core.info(`Added asset: ${targetFileName} (${size} bytes)`);
} catch (error) {
- core.setFailed(`Failed to process asset ${fileName}: ${getErrorMessage(error)}`);
+ core.setFailed(`${ERR_API}: Failed to process asset ${fileName}: ${getErrorMessage(error)}`);
return;
}
}
@@ -186,7 +187,7 @@ async function main() {
core.info("No new assets to upload");
}
} catch (error) {
- core.setFailed(`Failed to upload assets: ${getErrorMessage(error)}`);
+ core.setFailed(`${ERR_API}: Failed to upload assets: ${getErrorMessage(error)}`);
return;
}
diff --git a/actions/setup/js/validate_context_variables.cjs b/actions/setup/js/validate_context_variables.cjs
index 6c4f5a0550..cd0a78b649 100644
--- a/actions/setup/js/validate_context_variables.cjs
+++ b/actions/setup/js/validate_context_variables.cjs
@@ -42,6 +42,7 @@
*/
const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_VALIDATION } = require("./error_codes.cjs");
/**
* List of numeric context variable paths to validate
@@ -188,7 +189,7 @@ async function main() {
core.info("✅ All context variables validated successfully");
} catch (error) {
const errorMessage = getErrorMessage(error);
- core.setFailed(`Context variable validation failed: ${errorMessage}`);
+ core.setFailed(`${ERR_VALIDATION}: Context variable validation failed: ${errorMessage}`);
throw error;
}
}
diff --git a/actions/setup/js/validate_lockdown_requirements.cjs b/actions/setup/js/validate_lockdown_requirements.cjs
index ac97bfe334..ea03f5b2e8 100644
--- a/actions/setup/js/validate_lockdown_requirements.cjs
+++ b/actions/setup/js/validate_lockdown_requirements.cjs
@@ -14,6 +14,7 @@
* @param {any} core - GitHub Actions core library
* @returns {void}
*/
+const { ERR_VALIDATION } = require("./error_codes.cjs");
function validateLockdownRequirements(core) {
// Check if lockdown mode is explicitly enabled (set to "true" in frontmatter)
const lockdownEnabled = process.env.GITHUB_MCP_LOCKDOWN_EXPLICIT === "true";
diff --git a/actions/setup/js/validate_secrets.cjs b/actions/setup/js/validate_secrets.cjs
index f7ca3a01f7..cf89507740 100644
--- a/actions/setup/js/validate_secrets.cjs
+++ b/actions/setup/js/validate_secrets.cjs
@@ -18,6 +18,7 @@ const { promisify } = require("util");
const { exec } = require("child_process");
const execAsync = promisify(exec);
const { getErrorMessage } = require("./error_helpers.cjs");
+const { ERR_VALIDATION } = require("./error_codes.cjs");
/**
* Test result status
@@ -741,7 +742,7 @@ async function main() {
core.info("✅ All configured secrets validated successfully!");
}
} catch (error) {
- core.setFailed(`Secret validation failed: ${getErrorMessage(error)}`);
+ core.setFailed(`${ERR_VALIDATION}: Secret validation failed: ${getErrorMessage(error)}`);
throw error;
}
}
diff --git a/actions/setup/setup.sh b/actions/setup/setup.sh
index a3cf8eb5f8..37023ad13b 100755
--- a/actions/setup/setup.sh
+++ b/actions/setup/setup.sh
@@ -157,6 +157,7 @@ SAFE_INPUTS_FILES=(
"generate_safe_inputs_config.cjs"
"setup_globals.cjs"
"error_helpers.cjs"
+ "error_codes.cjs"
"mcp_enhanced_errors.cjs"
"shim.cjs"
)
@@ -223,6 +224,7 @@ SAFE_OUTPUTS_FILES=(
"generate_compact_schema.cjs"
"setup_globals.cjs"
"error_helpers.cjs"
+ "error_codes.cjs"
"git_helpers.cjs"
"mcp_enhanced_errors.cjs"
"comment_limit_helpers.cjs"