]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
ci: Fix several robustness issues in claude-review workflow 41115/head
authorDaan De Meyer <daan@amutable.com>
Sun, 15 Mar 2026 20:53:01 +0000 (21:53 +0100)
committerDaan De Meyer <daan@amutable.com>
Mon, 16 Mar 2026 08:16:19 +0000 (09:16 +0100)
- Use github.paginate() for listComments to handle PRs with 100+ comments
- Make line optional in review schema to allow file-level comments
- Skip createReviewComment for comments without a line number
- Fix failed count to exclude skipped file-level comments
- Pass review result via env var instead of expression injection
- Use core.warning() instead of console.log() for JSON parse failures
- Fix MARKER insertion for single-line summaries that have no newline
- Require "@claude review" instead of just "@claude" to trigger

Co-developed-by: Claude <claude@anthropic.com>
.github/workflows/claude-review.yml

index c29028569bf00fcd6c0c4900a0ff1a3e721a29c4..ef5afe620eda9874be82bd749fc3a6ef1a7d92db 100644 (file)
@@ -32,13 +32,13 @@ jobs:
       github.repository_owner == 'systemd' &&
       ((github.event_name == 'issue_comment' &&
         github.event.issue.pull_request &&
-        contains(github.event.comment.body, '@claude') &&
+        contains(github.event.comment.body, '@claude review') &&
         contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association)) ||
        (github.event_name == 'pull_request_review_comment' &&
-        contains(github.event.comment.body, '@claude') &&
+        contains(github.event.comment.body, '@claude review') &&
         contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association)) ||
        (github.event_name == 'pull_request_review' &&
-        contains(github.event.review.body, '@claude') &&
+        contains(github.event.review.body, '@claude review') &&
         contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.review.author_association)))
 
     permissions:
@@ -71,12 +71,10 @@ jobs:
             const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
             const MARKER = "<!-- claude-pr-review -->";
 
-            const {data: issueComments} = await github.rest.issues.listComments({
-              owner,
-              repo,
-              issue_number: prNumber,
-              per_page: 100,
-            });
+            const issueComments = await github.paginate(
+              github.rest.issues.listComments,
+              { owner, repo, issue_number: prNumber, per_page: 100 },
+            );
 
             const existing = issueComments.find((c) => c.body && c.body.includes(MARKER));
 
@@ -146,7 +144,7 @@ jobs:
                   "type": "array",
                   "items": {
                     "type": "object",
-                    "required": ["file", "line", "severity", "body"],
+                    "required": ["file", "severity", "body"],
                     "properties": {
                       "file": {
                         "type": "string",
@@ -331,6 +329,7 @@ jobs:
         uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
         env:
           STRUCTURED_OUTPUT: ${{ needs.review.outputs.structured_output }}
+          REVIEW_RESULT: ${{ needs.review.result }}
           PR_NUMBER: ${{ needs.setup.outputs.pr_number }}
           HEAD_SHA: ${{ needs.setup.outputs.head_sha }}
           COMMENT_ID: ${{ needs.setup.outputs.comment_id }}
@@ -346,7 +345,7 @@ jobs:
 
             /* If the review job failed or was cancelled, update the tracking
              * comment to reflect that and bail out. */
-            if ("${{ needs.review.result }}" !== "success") {
+            if (process.env.REVIEW_RESULT !== "success") {
               await github.rest.issues.updateComment({
                 owner,
                 repo,
@@ -372,7 +371,7 @@ jobs:
                 if (typeof review.summary === "string")
                   summary = review.summary;
               } catch (e) {
-                console.log(`Failed to parse structured output: ${e.message}`);
+                core.warning(`Failed to parse structured output: ${e.message}`);
               }
             }
 
@@ -382,8 +381,13 @@ jobs:
              * comments is handled by Claude in the prompt, so we just post whatever
              * it returns. Using individual comments (rather than a review) means
              * re-runs only add new comments instead of creating a whole new review. */
+            const inlineComments = comments.filter((c) => c.line);
+            const skipped = comments.length - inlineComments.length;
+            if (skipped > 0)
+              console.log(`Skipping ${skipped} file-level comment(s) (no line number).`);
+
             let posted = 0;
-            for (const c of comments) {
+            for (const c of inlineComments) {
               console.log(`  Posting comment on ${c.file}:${c.line}`);
               try {
                 await github.rest.pulls.createReviewComment({
@@ -404,19 +408,19 @@ jobs:
             }
 
             if (posted > 0)
-              console.log(`Posted ${posted}/${comments.length} inline comment(s).`);
-            else if (comments.length > 0)
-              console.log(`Could not post any of ${comments.length} inline comment(s) — see warnings above.`);
+              console.log(`Posted ${posted}/${inlineComments.length} inline comment(s).`);
+            else if (inlineComments.length > 0)
+              console.log(`Could not post any of ${inlineComments.length} inline comment(s) — see warnings above.`);
             else
               console.log("No inline comments to post.");
 
-            const failed = comments.length > 0 && posted < comments.length;
+            const failed = inlineComments.length > 0 && posted < inlineComments.length;
 
             /* Update the tracking comment with Claude's summary. */
             if (!summary)
               summary = "Claude review: no issues found :tada:\n\n" + MARKER;
             else if (!summary.includes(MARKER))
-              summary = summary.replace(/\n/, `\n${MARKER}\n`);
+              summary += "\n\n" + MARKER;
             summary += `\n\n[Workflow run](${runUrl})`;
 
             await github.rest.issues.updateComment({