feat(actions): add reusable code-review composite action#5
Conversation
Bundles the async-parallel PR review flow that consumes `review-code`
as a single GitHub Action so downstream protoLabs repos don't each
re-implement the workflow plumbing.
## What's in here
- `.github/actions/code-review/action.yml` — composite action that runs
`review-code init → map → run → report` through the gateway and posts
findings as a sticky PR comment. `continue-on-error: true` on the
review step keeps it non-blocking. Marker-based dedup on the comment
so re-runs `PATCH` the existing comment rather than stacking new ones.
- `.github/actions/code-review/post-findings.mjs` — sticky comment poster
used by the composite action.
- README: documents the action under "Model-backed code review" with
inputs table, behavior notes, and required secrets.
## Why a composite action instead of leaving each repo to write its own
protoMaker already shipped a workflow that did exactly this; quinn,
AionUi, and any other consumer would copy-paste the same YAML. The
sticky-comment script, the env-var translation, the timeout config,
the `continue-on-error` choice — none of it is repo-specific. Owning
the plumbing here keeps it consistent across consumers and lets bug
fixes flow on a version bump instead of per-repo PRs.
## Usage
```yaml
- uses: protoLabsAI/release-tools/.github/actions/code-review@main
with:
gateway-api-key: ${{ secrets.GATEWAY_API_KEY }}
gateway-base-url: ${{ secrets.GATEWAY_BASE_URL }}
```
Inputs: gateway-api-key (required), gateway-base-url, model, timeout-ms,
pr-number, github-token, release-tools-ref. Outputs: findings,
report-path.
## Validation
- YAML lints cleanly (js-yaml parse).
- `node --check` clean on post-findings.mjs.
- review-code itself is already validated against the gateway from
protoMaker (PR #3615) — local end-to-end run with the same secrets
this action expects.
WalkthroughThis PR introduces a new composite GitHub Action for ChangesCode Review Workflow Action
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/actions/code-review/action.yml:
- Around line 80-83: Replace the fixed /tmp/code-review.md with a unique per-run
temp file and update the subsequent existence check and GITHUB_OUTPUT write to
use that variable; specifically change the npx review-code command invocation
(the line calling "npx ... review-code report --output /tmp/code-review.md") to
write to a temp file created at runtime (e.g., via mktemp or using
RUNNER_TEMP/GITHUB_RUN_ID) and then change the if check and the echo that sets
report_path to reference that same temp variable instead of the hard-coded path,
optionally also ensure the script verifies the file is non-empty before
exporting report_path to avoid using stale/empty files.
In @.github/actions/code-review/post-findings.mjs:
- Around line 56-58: The current code assigns existingId = match.id from
gh(['pr','view', prNumber, '--json', 'comments']) but then PATCHes
/repos/${owner}/${repo}/issues/comments/${existingId}, which requires the REST
numeric comment ID; update the lookup so existingId is the REST comment id:
replace the gh pr view comments fetch with a call to the REST API (e.g., gh api
repos/:owner/:repo/issues/:issue_number/comments) and find the comment whose
body includes MARKER, then set existingId to that comment's id before calling
the PATCH endpoint; ensure the variables referenced are existingId, match (or
the found comment), and the PATCH URL
/repos/${owner}/${repo}/issues/comments/${existingId}.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 2ed5236c-dc70-490b-afa7-fb08dee1ed11
⛔ Files ignored due to path filters (1)
README.mdis excluded by!*.md
📒 Files selected for processing (2)
.github/actions/code-review/action.yml.github/actions/code-review/post-findings.mjs
| npx --yes -p "github:protoLabsAI/release-tools#${{ inputs.release-tools-ref }}" review-code report --output /tmp/code-review.md 2>&1 | tee /tmp/report.log || true | ||
| if [ -f /tmp/code-review.md ]; then | ||
| echo "report_path=/tmp/code-review.md" >> "$GITHUB_OUTPUT" | ||
| fi |
There was a problem hiding this comment.
Avoid stale report reuse by using a unique temp file per run.
Line 80 writes to a fixed /tmp/code-review.md and Line 81 only checks existence. On persistent runners, a previous file can be reused if report generation fails, leading to stale findings being posted.
Proposed fix
- npx --yes -p "github:protoLabsAI/release-tools#${{ inputs.release-tools-ref }}" review-code report --output /tmp/code-review.md 2>&1 | tee /tmp/report.log || true
- if [ -f /tmp/code-review.md ]; then
- echo "report_path=/tmp/code-review.md" >> "$GITHUB_OUTPUT"
+ report_file="${RUNNER_TEMP}/code-review-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}.md"
+ rm -f "$report_file"
+ npx --yes -p "github:protoLabsAI/release-tools#${{ inputs.release-tools-ref }}" review-code report --output "$report_file" 2>&1 | tee /tmp/report.log || true
+ if [ -s "$report_file" ]; then
+ echo "report_path=$report_file" >> "$GITHUB_OUTPUT"
fi📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| npx --yes -p "github:protoLabsAI/release-tools#${{ inputs.release-tools-ref }}" review-code report --output /tmp/code-review.md 2>&1 | tee /tmp/report.log || true | |
| if [ -f /tmp/code-review.md ]; then | |
| echo "report_path=/tmp/code-review.md" >> "$GITHUB_OUTPUT" | |
| fi | |
| report_file="${RUNNER_TEMP}/code-review-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}.md" | |
| rm -f "$report_file" | |
| npx --yes -p "github:protoLabsAI/release-tools#${{ inputs.release-tools-ref }}" review-code report --output "$report_file" 2>&1 | tee /tmp/report.log || true | |
| if [ -s "$report_file" ]; then | |
| echo "report_path=$report_file" >> "$GITHUB_OUTPUT" | |
| fi |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/actions/code-review/action.yml around lines 80 - 83, Replace the
fixed /tmp/code-review.md with a unique per-run temp file and update the
subsequent existence check and GITHUB_OUTPUT write to use that variable;
specifically change the npx review-code command invocation (the line calling
"npx ... review-code report --output /tmp/code-review.md") to write to a temp
file created at runtime (e.g., via mktemp or using RUNNER_TEMP/GITHUB_RUN_ID)
and then change the if check and the echo that sets report_path to reference
that same temp variable instead of the hard-coded path, optionally also ensure
the script verifies the file is non-empty before exporting report_path to avoid
using stale/empty files.
| const comments = JSON.parse(gh(['pr', 'view', prNumber, '--json', 'comments'])); | ||
| const match = (comments.comments ?? []).find((c) => c.body?.includes(MARKER)); | ||
| if (match) existingId = match.id; |
There was a problem hiding this comment.
❓ Verification inconclusive
🏁 Script executed:
#!/bin/bash
# Verify whether comment IDs from `gh pr view --json comments` are REST-patchable IDs.
set -euo pipefail
PR_NUMBER="${PR_NUMBER:?set PR_NUMBER}"
# Shows the shape of IDs returned by pr view
gh pr view "$PR_NUMBER" --json comments --jq '.comments[] | {id, url}' | head -n 5
# Shows numeric IDs from REST issue comments for the same PR
gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" --jq '.[] | {id, html_url}' | head -n 5Repository: protoLabsAI/release-tools
Length of output: 113
Confirm PATCH /issues/comments/{comment_id} uses REST numeric IDs; existingId currently comes from gh pr view --json comments.
post-findings.mjs sets existingId = match.id from gh pr view <prNumber> --json comments, then PATCHes /repos/${owner}/${repo}/issues/comments/${existingId}. The {comment_id} path for that REST endpoint must be the REST comment ID; ensure the id returned by gh pr view --json comments is numeric/REST-compatible, or the script may not update the existing comment.
Proposed fix (lookup via REST comments so IDs are patchable)
-let existingId = null;
+let existingId = null;
try {
- const comments = JSON.parse(gh(['pr', 'view', prNumber, '--json', 'comments']));
- const match = (comments.comments ?? []).find((c) => c.body?.includes(MARKER));
+ const comments = JSON.parse(
+ gh([
+ 'api',
+ `repos/${process.env.GITHUB_REPOSITORY}/issues/${prNumber}/comments`,
+ '--paginate',
+ ]),
+ );
+ const match = (comments ?? []).find((c) => c.body?.includes(MARKER));
if (match) existingId = match.id;
} catch (err) {
console.error(`Failed to list comments (continuing as new): ${err.message}`);
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/actions/code-review/post-findings.mjs around lines 56 - 58, The
current code assigns existingId = match.id from gh(['pr','view', prNumber,
'--json', 'comments']) but then PATCHes
/repos/${owner}/${repo}/issues/comments/${existingId}, which requires the REST
numeric comment ID; update the lookup so existingId is the REST comment id:
replace the gh pr view comments fetch with a call to the REST API (e.g., gh api
repos/:owner/:repo/issues/:issue_number/comments) and find the comment whose
body includes MARKER, then set existingId to that comment's id before calling
the PATCH endpoint; ensure the variables referenced are existingId, match (or
the found comment), and the PATCH URL
/repos/${owner}/${repo}/issues/comments/${existingId}.
Bundles the async-parallel PR review flow that consumes
review-codeas a single GitHub Action so downstream protoLabs repos don't each re-implement the workflow plumbing.What's in here
.github/actions/code-review/action.yml— composite action that runsreview-code init → map → run → reportthrough the gateway and posts findings as a sticky PR comment.continue-on-error: trueon the review step keeps it non-blocking. Marker-based dedup on the comment so re-runsPATCHthe existing comment rather than stacking new ones..github/actions/code-review/post-findings.mjs— sticky comment poster used by the composite action.Why a composite action instead of leaving each repo to write its own
protoMaker already shipped a workflow that did exactly this (PR #3615 in protoMaker); quinn, AionUi, and any other consumer would copy-paste the same YAML. The sticky-comment script, the env-var translation, the timeout config, the
continue-on-errorchoice — none of it is repo-specific. Owning the plumbing here keeps it consistent across consumers and lets bug fixes flow on a version bump instead of per-repo PRs.Usage
Inputs
gateway-api-keyOPENAI_API_KEY.gateway-base-urlhttps://api.proto-labs.ai/v1— appended automatically.modelprotolabs/smart(review-code default)timeout-ms300000pr-numbergithub-token${{ github.token }}release-tools-refmainValidation
node --checkclean onpost-findings.mjs.review-codeitself already validated against the gateway from protoMaker (PR #3615) — local end-to-end run with the same secrets this action expects.Follow-up
protoMaker will get a separate slim PR that drops its bespoke
code-review.yml(54 lines + sticky-comment script) and replaces it with a 5-line workflow calling this action.Summary by CodeRabbit
New Features