@@ -269,7 +269,17 @@ jobs:
269269 --allowedTools View,GlobTool,GrepTool
270270 anthropic_api_key : ${{ secrets.ANTHROPIC_API_KEY }}
271271
272- # Step 7: Post review as PR comment (skip if dry_run)
272+ # Step 7: Save execution log as artifact for debugging
273+ - name : Upload execution log
274+ if : always()
275+ uses : actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
276+ with :
277+ name : claude-execution-log-${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.issue.number }}
278+ path : ${{ steps.claude_review.outputs.execution_file }}
279+ retention-days : 7
280+ if-no-files-found : warn
281+
282+ # Step 8: Post review as PR comment (skip if dry_run)
273283 - name : Post review comment
274284 if : ${{ !(github.event_name == 'workflow_dispatch' && inputs.dry_run) }}
275285 uses : actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
@@ -285,67 +295,27 @@ jobs:
285295 let reviewBody = '';
286296
287297 try {
288- // Read the execution log
289- const fileContent = fs.readFileSync(process.env.EXECUTION_FILE, 'utf8');
290- const executionLog = JSON.parse(fileContent);
298+ // Read the execution log (array of messages from claude-code-base-action)
299+ const executionLog = JSON.parse(fs.readFileSync(process.env.EXECUTION_FILE, 'utf8'));
291300
292- // Log structure for debugging
293- console.log('Execution log type:', Array.isArray(executionLog) ? 'array' : typeof executionLog);
294301 if (!Array.isArray(executionLog)) {
295- console.log('Execution log keys:', Object.keys(executionLog) );
302+ throw new Error('Expected array format from claude-code-base-action' );
296303 }
297304
298- // Helper function to extract text from assistant messages
299- const extractFromMessages = (messages) => {
300- const assistantMessages = messages.filter(m => m.role === 'assistant');
301- if (assistantMessages.length > 0) {
302- const lastMessage = assistantMessages[assistantMessages.length - 1];
303- if (typeof lastMessage.content === 'string') {
304- return lastMessage.content;
305- } else if (Array.isArray(lastMessage.content)) {
306- return lastMessage.content
307- .filter(block => block.type === 'text')
308- .map(block => block.text)
309- .join('\n\n');
310- }
311- }
312- return null;
313- };
314-
315- // Try different possible structures from claude-code-base-action
316- if (Array.isArray(executionLog)) {
317- // claude-code-base-action writes messages array directly to file
318- const extracted = extractFromMessages(executionLog);
319- if (extracted) {
320- reviewBody = extracted;
321- } else {
322- console.log('No assistant messages found in array');
323- reviewBody = '⚠️ No assistant response found in execution log.';
324- }
325- } else if (executionLog.result) {
326- // Direct result field
327- reviewBody = executionLog.result;
328- } else if (executionLog.output) {
329- // Output field
330- reviewBody = executionLog.output;
331- } else if (executionLog.response) {
332- // Response field
333- reviewBody = executionLog.response;
334- } else if (executionLog.messages && Array.isArray(executionLog.messages)) {
335- // Messages array inside object
336- const extracted = extractFromMessages(executionLog.messages);
337- if (extracted) {
338- reviewBody = extracted;
339- } else {
340- reviewBody = '⚠️ No assistant response found in messages.';
341- }
342- } else if (typeof executionLog === 'string') {
343- // Plain string
344- reviewBody = executionLog;
305+ // Find the "result" message which contains the final review
306+ const resultMessage = executionLog.find(m => m.type === 'result');
307+
308+ if (!resultMessage) {
309+ reviewBody = '⚠️ No result message found in execution log.';
310+ } else if (resultMessage.subtype === 'success' && resultMessage.result) {
311+ reviewBody = resultMessage.result;
312+ } else if (resultMessage.is_error || resultMessage.subtype === 'error') {
313+ const errorInfo = resultMessage.result || resultMessage.error || 'Unknown error';
314+ reviewBody = `❌ **Claude Code encountered an error:**\n\n${errorInfo}`;
315+ } else if (resultMessage.result) {
316+ reviewBody = `⚠️ **Review completed with status: ${resultMessage.subtype}**\n\n${resultMessage.result}`;
345317 } else {
346- // Fallback: stringify the whole thing
347- console.log('Unknown format, raw content:', JSON.stringify(executionLog).substring(0, 500));
348- reviewBody = '⚠️ Could not parse Claude response format. Check workflow logs for details.';
318+ reviewBody = '⚠️ Claude completed but produced no review output.';
349319 }
350320 } catch (error) {
351321 console.error('Error parsing execution log:', error);
@@ -385,35 +355,37 @@ jobs:
385355 body: fullComment
386356 });
387357
388- # Step 7b : Print review to logs if dry_run
358+ # Step 8b : Print review to logs if dry_run
389359 - name : Print review to logs (dry run)
390360 if : ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }}
391361 env :
392362 EXECUTION_FILE : ${{ steps.claude_review.outputs.execution_file }}
393363 run : |
394364 echo "=== DRY RUN - Review would be posted as comment ==="
395365 echo ""
396- echo "=== Execution file structure ==="
397- # Check if it's an array (claude-code-base-action format) or object
398- if jq -e 'type == "array"' "$EXECUTION_FILE" >/dev/null 2>&1; then
399- echo "Format: array of messages (length: $(jq 'length' "$EXECUTION_FILE"))"
400- else
401- jq 'keys' "$EXECUTION_FILE" 2>/dev/null || echo "Not valid JSON or file not found"
402- fi
403- echo ""
404- echo "=== Attempting to extract review ==="
405- # Try array format first (claude-code-base-action), then object formats
406- if jq -e 'type == "array"' "$EXECUTION_FILE" >/dev/null 2>&1; then
407- # Array format: extract last assistant message content
408- jq -r '[.[] | select(.role == "assistant")] | last | if .content | type == "string" then .content elif .content | type == "array" then [.content[] | select(.type == "text") | .text] | join("\n\n") else "Could not extract" end' "$EXECUTION_FILE" 2>/dev/null || cat "$EXECUTION_FILE"
366+ echo "=== Extracting review from result message ==="
367+ # Find the "result" message and extract based on subtype
368+ RESULT_MSG=$(jq '[.[] | select(.type == "result")] | .[0]' "$EXECUTION_FILE" 2>/dev/null)
369+ if [ "$RESULT_MSG" = "null" ] || [ -z "$RESULT_MSG" ]; then
370+ echo "⚠️ No result message found"
409371 else
410- # Object format: try different possible paths
411- jq -r '.result // .output // .response // .messages[-1].content // "Could not extract"' "$EXECUTION_FILE" 2>/dev/null || cat "$EXECUTION_FILE"
372+ SUBTYPE=$(echo "$RESULT_MSG" | jq -r '.subtype // "unknown"')
373+ echo "Status: $SUBTYPE"
374+ echo ""
375+ if [ "$SUBTYPE" = "success" ]; then
376+ echo "$RESULT_MSG" | jq -r '.result // "No result field"'
377+ elif [ "$SUBTYPE" = "error" ]; then
378+ echo "❌ Error:"
379+ echo "$RESULT_MSG" | jq -r '.result // .error // "Unknown error"'
380+ else
381+ echo "⚠️ Status: $SUBTYPE"
382+ echo "$RESULT_MSG" | jq -r '.result // "No result"'
383+ fi
412384 fi
413385 echo ""
414386 echo "=== End of review ==="
415387
416- # Step 8 : Update reaction on completion (only for issue_comment trigger)
388+ # Step 9 : Update reaction on completion (only for issue_comment trigger)
417389 - name : Update reaction on success
418390 if : success() && github.event_name == 'issue_comment'
419391 uses : actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
0 commit comments