diff --git a/action.yml b/action.yml index 4e148e4..dfd64c9 100644 --- a/action.yml +++ b/action.yml @@ -147,6 +147,8 @@ runs: CLAUDE_SUCCESS: ${{ steps.claude-code.outputs.conclusion == 'success' }} OUTPUT_FILE: ${{ steps.claude-code.outputs.execution_file || '' }} TRIGGER_USERNAME: ${{ github.event.comment.user.login || github.event.issue.user.login || github.event.pull_request.user.login || github.event.sender.login || github.triggering_actor || github.actor || '' }} + PREPARE_SUCCESS: ${{ steps.prepare.outcome == 'success' }} + PREPARE_ERROR: ${{ steps.prepare.outputs.prepare_error || '' }} - name: Display Claude Code Report if: steps.prepare.outputs.contains_trigger == 'true' && steps.claude-code.outputs.execution_file != '' diff --git a/src/entrypoints/prepare.ts b/src/entrypoints/prepare.ts index 1cce680..1470798 100644 --- a/src/entrypoints/prepare.ts +++ b/src/entrypoints/prepare.ts @@ -92,7 +92,10 @@ async function run() { ); core.setOutput("mcp_config", mcpConfig); } catch (error) { - core.setFailed(`Prepare step failed with error: ${error}`); + const errorMessage = error instanceof Error ? error.message : String(error); + core.setFailed(`Prepare step failed with error: ${errorMessage}`); + // Also output the clean error message for the action to capture + core.setOutput("prepare_error", errorMessage); process.exit(1); } } diff --git a/src/entrypoints/update-comment-link.ts b/src/entrypoints/update-comment-link.ts index daa2cec..d74bcd5 100644 --- a/src/entrypoints/update-comment-link.ts +++ b/src/entrypoints/update-comment-link.ts @@ -145,38 +145,48 @@ async function run() { duration_api_ms?: number; } | null = null; let actionFailed = false; + let errorDetails: string | undefined; - // Check for existence of output file and parse it if available - try { - const outputFile = process.env.OUTPUT_FILE; - if (outputFile) { - const fileContent = await fs.readFile(outputFile, "utf8"); - const outputData = JSON.parse(fileContent); + // First check if prepare step failed + const prepareSuccess = process.env.PREPARE_SUCCESS !== "false"; + const prepareError = process.env.PREPARE_ERROR; - // Output file is an array, get the last element which contains execution details - if (Array.isArray(outputData) && outputData.length > 0) { - const lastElement = outputData[outputData.length - 1]; - if ( - lastElement.role === "system" && - "cost_usd" in lastElement && - "duration_ms" in lastElement - ) { - executionDetails = { - cost_usd: lastElement.cost_usd, - duration_ms: lastElement.duration_ms, - duration_api_ms: lastElement.duration_api_ms, - }; + if (!prepareSuccess && prepareError) { + actionFailed = true; + errorDetails = prepareError; + } else { + // Check for existence of output file and parse it if available + try { + const outputFile = process.env.OUTPUT_FILE; + if (outputFile) { + const fileContent = await fs.readFile(outputFile, "utf8"); + const outputData = JSON.parse(fileContent); + + // Output file is an array, get the last element which contains execution details + if (Array.isArray(outputData) && outputData.length > 0) { + const lastElement = outputData[outputData.length - 1]; + if ( + lastElement.role === "system" && + "cost_usd" in lastElement && + "duration_ms" in lastElement + ) { + executionDetails = { + cost_usd: lastElement.cost_usd, + duration_ms: lastElement.duration_ms, + duration_api_ms: lastElement.duration_api_ms, + }; + } } } - } - // Check if the action failed by looking at the exit code or error marker - const claudeSuccess = process.env.CLAUDE_SUCCESS !== "false"; - actionFailed = !claudeSuccess; - } catch (error) { - console.error("Error reading output file:", error); - // If we can't read the file, check for any failure markers - actionFailed = process.env.CLAUDE_SUCCESS === "false"; + // Check if the Claude action failed + const claudeSuccess = process.env.CLAUDE_SUCCESS !== "false"; + actionFailed = !claudeSuccess; + } catch (error) { + console.error("Error reading output file:", error); + // If we can't read the file, check for any failure markers + actionFailed = process.env.CLAUDE_SUCCESS === "false"; + } } // Prepare input for updateCommentBody function @@ -189,6 +199,7 @@ async function run() { prLink, branchName: shouldDeleteBranch ? undefined : claudeBranch, triggerUsername, + errorDetails, }; const updatedBody = updateCommentBody(commentInput); diff --git a/src/github/operations/comment-logic.ts b/src/github/operations/comment-logic.ts index 6c2bad4..6a4551a 100644 --- a/src/github/operations/comment-logic.ts +++ b/src/github/operations/comment-logic.ts @@ -15,6 +15,7 @@ export type CommentUpdateInput = { prLink?: string; branchName?: string; triggerUsername?: string; + errorDetails?: string; }; export function ensureProperlyEncodedUrl(url: string): string | null { @@ -75,6 +76,7 @@ export function updateCommentBody(input: CommentUpdateInput): string { actionFailed, branchName, triggerUsername, + errorDetails, } = input; // Extract content from the original comment body @@ -177,7 +179,14 @@ export function updateCommentBody(input: CommentUpdateInput): string { } // Build the new body with blank line between header and separator - let newBody = `${header}${links}\n\n---\n`; + let newBody = `${header}${links}`; + + // Add error details if available + if (actionFailed && errorDetails) { + newBody += `\n\n\`\`\`\n${errorDetails}\n\`\`\``; + } + + newBody += `\n\n---\n`; // Clean up the body content // Remove any existing View job run, branch links from the bottom diff --git a/test/comment-logic.test.ts b/test/comment-logic.test.ts index 3b86908..82fec08 100644 --- a/test/comment-logic.test.ts +++ b/test/comment-logic.test.ts @@ -39,6 +39,25 @@ describe("updateCommentBody", () => { expect(result).toContain("**Claude encountered an error after 45s**"); }); + it("includes error details when provided", () => { + const input = { + ...baseInput, + currentBody: "Claude Code is working...", + actionFailed: true, + executionDetails: { duration_ms: 45000 }, + errorDetails: "Failed to fetch issue data", + }; + + const result = updateCommentBody(input); + expect(result).toContain("**Claude encountered an error after 45s**"); + expect(result).toContain("[View job]"); + expect(result).toContain("```\nFailed to fetch issue data\n```"); + // Ensure error details come after the header/links + const errorIndex = result.indexOf("```"); + const headerIndex = result.indexOf("**Claude encountered an error"); + expect(errorIndex).toBeGreaterThan(headerIndex); + }); + it("handles username extraction from content when not provided", () => { const input = { ...baseInput,