diff --git a/.changeset/patch-resolve-add-comment-temp-id.md b/.changeset/patch-resolve-add-comment-temp-id.md new file mode 100644 index 00000000000..043b6bf2053 --- /dev/null +++ b/.changeset/patch-resolve-add-comment-temp-id.md @@ -0,0 +1,5 @@ +--- +"gh-aw": patch +--- + +Fix the `add_comment` safe output handler so it resolves temporary item IDs before validating the issue number. diff --git a/actions/setup/js/add_comment.cjs b/actions/setup/js/add_comment.cjs index a5d6cfef068..c84e4f9ee13 100644 --- a/actions/setup/js/add_comment.cjs +++ b/actions/setup/js/add_comment.cjs @@ -7,7 +7,7 @@ const { generateFooterWithMessages } = require("./messages_footer.cjs"); const { getRepositoryUrl } = require("./get_repository_url.cjs"); -const { replaceTemporaryIdReferences } = require("./temporary_id.cjs"); +const { replaceTemporaryIdReferences, loadTemporaryIdMapFromResolved, resolveRepoIssueTarget } = require("./temporary_id.cjs"); const { getTrackerID } = require("./get_tracker_id.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); const { resolveTarget } = require("./safe_output_helpers.cjs"); @@ -351,10 +351,21 @@ async function main(config = {}) { // Check if item_number was explicitly provided in the message if (item.item_number !== undefined && item.item_number !== null) { - // Use the explicitly provided item_number - itemNumber = typeof item.item_number === "number" ? item.item_number : parseInt(String(item.item_number), 10); + // Resolve temporary IDs if present + const resolvedTarget = resolveRepoIssueTarget(item.item_number, temporaryIdMap, repoParts.owner, repoParts.repo); - if (isNaN(itemNumber) || itemNumber <= 0) { + // Check if this is an unresolved temporary ID + if (resolvedTarget.wasTemporaryId && !resolvedTarget.resolved) { + core.info(`Deferring add_comment: unresolved temporary ID (${item.item_number})`); + return { + success: false, + deferred: true, + error: resolvedTarget.errorMessage || `Unresolved temporary ID: ${item.item_number}`, + }; + } + + // Check for other resolution errors (including null resolved) + if (resolvedTarget.errorMessage || !resolvedTarget.resolved) { core.warning(`Invalid item_number specified: ${item.item_number}`); return { success: false, @@ -362,6 +373,8 @@ async function main(config = {}) { }; } + // Use the resolved issue number (safe to access because we checked above) + itemNumber = resolvedTarget.resolved.number; core.info(`Using explicitly provided item_number: #${itemNumber}`); } else { // Check if this is a discussion context diff --git a/actions/setup/js/add_comment.test.cjs b/actions/setup/js/add_comment.test.cjs index 71936283a39..e13e57dcc27 100644 --- a/actions/setup/js/add_comment.test.cjs +++ b/actions/setup/js/add_comment.test.cjs @@ -952,4 +952,150 @@ describe("add_comment", () => { expect(graphqlCallCount).toBe(1); }); }); + + describe("temporary ID resolution", () => { + it("should resolve temporary ID in item_number field", async () => { + const addCommentScript = fs.readFileSync(path.join(__dirname, "add_comment.cjs"), "utf8"); + + let capturedIssueNumber = null; + mockGithub.rest.issues.createComment = async params => { + capturedIssueNumber = params.issue_number; + return { + data: { + id: 12345, + html_url: `https://github.com/owner/repo/issues/${params.issue_number}#issuecomment-12345`, + }, + }; + }; + + const handler = await eval(`(async () => { ${addCommentScript}; return await main({}); })()`); + + const message = { + type: "add_comment", + item_number: "aw_test01", + body: "Comment on issue created with temporary ID", + }; + + // Provide resolved temporary ID + const resolvedTemporaryIds = { + aw_test01: { repo: "owner/repo", number: 42 }, + }; + + const result = await handler(message, resolvedTemporaryIds); + + expect(result.success).toBe(true); + expect(capturedIssueNumber).toBe(42); + expect(result.itemNumber).toBe(42); + }); + + it("should defer when temporary ID is not yet resolved", async () => { + const addCommentScript = fs.readFileSync(path.join(__dirname, "add_comment.cjs"), "utf8"); + + const handler = await eval(`(async () => { ${addCommentScript}; return await main({}); })()`); + + const message = { + type: "add_comment", + item_number: "aw_test99", + body: "Comment on issue with unresolved temporary ID", + }; + + // Empty resolved map - temporary ID not yet resolved + const resolvedTemporaryIds = {}; + + const result = await handler(message, resolvedTemporaryIds); + + expect(result.success).toBe(false); + expect(result.deferred).toBe(true); + expect(result.error).toContain("aw_test99"); + }); + + it("should handle temporary ID with hash prefix", async () => { + const addCommentScript = fs.readFileSync(path.join(__dirname, "add_comment.cjs"), "utf8"); + + let capturedIssueNumber = null; + mockGithub.rest.issues.createComment = async params => { + capturedIssueNumber = params.issue_number; + return { + data: { + id: 12345, + html_url: `https://github.com/owner/repo/issues/${params.issue_number}#issuecomment-12345`, + }, + }; + }; + + const handler = await eval(`(async () => { ${addCommentScript}; return await main({}); })()`); + + const message = { + type: "add_comment", + item_number: "#aw_test02", + body: "Comment with hash prefix", + }; + + // Provide resolved temporary ID + const resolvedTemporaryIds = { + aw_test02: { repo: "owner/repo", number: 100 }, + }; + + const result = await handler(message, resolvedTemporaryIds); + + expect(result.success).toBe(true); + expect(capturedIssueNumber).toBe(100); + }); + + it("should handle invalid temporary ID format", async () => { + const addCommentScript = fs.readFileSync(path.join(__dirname, "add_comment.cjs"), "utf8"); + + const handler = await eval(`(async () => { ${addCommentScript}; return await main({}); })()`); + + const message = { + type: "add_comment", + item_number: "aw_", // Invalid: too short + body: "Comment with invalid temporary ID", + }; + + const resolvedTemporaryIds = {}; + + const result = await handler(message, resolvedTemporaryIds); + + expect(result.success).toBe(false); + expect(result.error).toContain("Invalid item_number specified"); + }); + + it("should replace temporary IDs in comment body", async () => { + const addCommentScript = fs.readFileSync(path.join(__dirname, "add_comment.cjs"), "utf8"); + + let capturedBody = null; + mockGithub.rest.issues.createComment = async params => { + capturedBody = params.body; + return { + data: { + id: 12345, + html_url: `https://github.com/owner/repo/issues/${params.issue_number}#issuecomment-12345`, + }, + }; + }; + + const handler = await eval(`(async () => { ${addCommentScript}; return await main({}); })()`); + + const message = { + type: "add_comment", + item_number: 42, + body: "References: #aw_test01 and #aw_test02", + }; + + // Provide resolved temporary IDs + const resolvedTemporaryIds = { + aw_test01: { repo: "owner/repo", number: 100 }, + aw_test02: { repo: "owner/repo", number: 200 }, + }; + + const result = await handler(message, resolvedTemporaryIds); + + expect(result.success).toBe(true); + expect(capturedBody).toContain("#100"); + expect(capturedBody).toContain("#200"); + expect(capturedBody).not.toContain("aw_test01"); + expect(capturedBody).not.toContain("aw_test02"); + }); + }); });