feat: add lightweight Gemini advisory PR review workflow#125
feat: add lightweight Gemini advisory PR review workflow#125Aaravanand00 wants to merge 1 commit intohyperledger-identus:mainfrom
Conversation
Introduces a non-blocking Gemini AI review system that fetches diffs per file, handles pagination, and provides actionable feedback as advisory comments. Signed-off-by: Aaravanand00 <aaravanand5749@gmail.com>
|
There was a problem hiding this comment.
Pull request overview
Adds a lightweight GitHub Actions workflow plus a Node.js script to generate an advisory PR review using Gemini and post the result back to the pull request.
Changes:
- Introduces a new PR/command-triggered GitHub Actions workflow for Gemini advisory reviews.
- Adds a Node.js script that fetches PR diffs via GitHub API, calls Gemini, and posts the advisory feedback to the PR.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
.github/workflows/gemini-pr-review.yml |
New workflow that triggers on PR activity and /gemini-review comments, sets permissions, and runs the reviewer script. |
.github/scripts/gemini-pr-review.js |
New script that fetches PR file patches, filters/truncates diffs, calls Gemini with a structured prompt, and posts the advisory review. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const isIgnored = filename.endsWith('.md') || | ||
| filename.endsWith('.txt') || | ||
| filename.startsWith('.github/'); | ||
|
|
There was a problem hiding this comment.
The diff filtering currently ignores any file under .github/ (filename.startsWith('.github/')). That will exclude code changes in .github/scripts and other non-config logic stored under .github/, which conflicts with the stated goal of ignoring only docs/config. Consider narrowing the ignore list to specific paths (e.g., .github/workflows/, .github/dependabot.yml) or excluding only known non-code extensions, so actionable code in .github/ can still be reviewed.
| const response = await fetchWithTimeout(`https://api.github.com/repos/${repo}/pulls/${prNumber}/reviews`, { | ||
| method: 'POST', | ||
| headers: { | ||
| Authorization: `token ${token}`, | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| body: JSON.stringify({ | ||
| body: commentBody, | ||
| event: 'COMMENT' | ||
| }), | ||
| }); | ||
|
|
||
| if (!response.ok) { | ||
| console.warn('Pull Request Review API failed, falling back to Issue Comment API.'); | ||
| await postFallbackComment(repo, prNumber, token, commentBody); | ||
| } else { | ||
| console.log('Advisory review posted successfully.'); | ||
| } | ||
| } catch (err) { | ||
| console.error('Failed to post review:', err.message); | ||
| await postFallbackComment(repo, prNumber, token, commentBody); |
There was a problem hiding this comment.
postReview always creates a new PR review via the Reviews API. On synchronize (and on repeated /gemini-review runs) this will generate multiple advisory reviews and can clutter the PR timeline. Consider de-duping by using the Issues Comments API as the primary mechanism (you already have update-or-create logic in postFallbackComment) or by checking for an existing bot review/comment and updating/replacing it instead of posting a new one each run.
| const response = await fetchWithTimeout(`https://api.github.com/repos/${repo}/pulls/${prNumber}/reviews`, { | |
| method: 'POST', | |
| headers: { | |
| Authorization: `token ${token}`, | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| body: commentBody, | |
| event: 'COMMENT' | |
| }), | |
| }); | |
| if (!response.ok) { | |
| console.warn('Pull Request Review API failed, falling back to Issue Comment API.'); | |
| await postFallbackComment(repo, prNumber, token, commentBody); | |
| } else { | |
| console.log('Advisory review posted successfully.'); | |
| } | |
| } catch (err) { | |
| console.error('Failed to post review:', err.message); | |
| await postFallbackComment(repo, prNumber, token, commentBody); | |
| await postFallbackComment(repo, prNumber, token, commentBody); | |
| console.log('Advisory review comment upserted successfully.'); | |
| } catch (err) { | |
| console.error('Failed to post advisory review comment:', err.message); |
| if (!GITHUB_TOKEN || !GEMINI_API_KEY || !REPO || !GITHUB_EVENT_PATH) { | ||
| console.error('Required environment variables are missing.'); | ||
| process.exit(1); | ||
| } | ||
|
|
There was a problem hiding this comment.
For forked PRs (and other contexts), GEMINI_API_KEY won't be available; currently the script exits with code 1 when env vars are missing, which will mark the workflow run as failed and add noise even though the review is intended to be advisory. Consider treating missing GEMINI_API_KEY as a graceful skip (log and exit 0), while still failing only on genuinely unexpected errors when the secret is configured.
| if (!GITHUB_TOKEN || !GEMINI_API_KEY || !REPO || !GITHUB_EVENT_PATH) { | |
| console.error('Required environment variables are missing.'); | |
| process.exit(1); | |
| } | |
| if (!GITHUB_TOKEN || !REPO || !GITHUB_EVENT_PATH) { | |
| console.error('Required GitHub environment variables are missing.'); | |
| process.exit(1); | |
| } | |
| if (!GEMINI_API_KEY) { | |
| console.log('Skipping Gemini PR review: GEMINI_API_KEY is not available in this context.'); | |
| process.exit(0); | |
| } |
| name: Gemini Advisory PR Review | ||
|
|
||
| on: | ||
| pull_request: | ||
| types: [opened, synchronize, labeled] | ||
| issue_comment: | ||
| types: [created] | ||
|
|
There was a problem hiding this comment.
This workflow doesn't set a top-level permissions: {} block. In this repo, other workflows explicitly set empty default permissions and then grant the minimum job-level permissions (e.g., .github/workflows/ci.yml:11, codeql.yml:23). Adding permissions: {} at the workflow level reduces the chance of accidentally inheriting broader defaults if GitHub changes defaults or the workflow is extended later.
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '20' | ||
|
|
||
| - name: Run Gemini PR Review | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} | ||
| run: node .github/scripts/gemini-pr-review.js |
There was a problem hiding this comment.
Security: this workflow checks out the PR's code (actions/checkout default ref) and then executes a script from the checkout while GEMINI_API_KEY is provided as a secret. A PR author could modify .github/scripts/gemini-pr-review.js to exfiltrate the secret during the run. Use a trusted ref for checkout (e.g., the base branch/pull_request.base.sha), or avoid checkout entirely and run a trusted script (or move to pull_request_target with a safe checkout strategy).
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '20' | ||
|
|
||
| - name: Run Gemini PR Review | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} |
There was a problem hiding this comment.
Codebase convention/supply-chain hardening: other workflows in this repo pin actions to immutable SHAs and include a step-security/harden-runner step (e.g., .github/workflows/ci.yml, codeql.yml). Here actions/checkout@v4 and actions/setup-node@v4 are unpinned and there's no runner hardening, which weakens provenance/egress auditing. Consider adding the harden-runner step and pinning action versions to SHAs for consistency and security.
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| - name: Run Gemini PR Review | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} | |
| - name: Harden runner | |
| uses: step-security/harden-runner@c6295a65d1254861815972266d5933fd6e532bdf | |
| with: | |
| egress-policy: audit | |
| - name: Checkout repository | |
| uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 | |
| with: | |
| node-version: '20' | |
| - name: Run Gemini PR Review | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GEMINI_API_KEY: ${{ secrets.GITHUB_TOKEN }} |
| # Only run for pull requests or comments on pull requests | ||
| if: | | ||
| github.event_name == 'pull_request' || | ||
| (github.event_name == 'issue_comment' && github.event.issue.pull_request) |
There was a problem hiding this comment.
Operational/perf: for issue_comment events, the job runs for every PR comment and only exits later inside the Node script unless the comment contains /gemini-review. You can reduce wasted runner time by adding an additional workflow/job-level condition that checks the comment body (e.g., contains(github.event.comment.body, '/gemini-review')) when github.event_name == 'issue_comment'.
| # Only run for pull requests or comments on pull requests | |
| if: | | |
| github.event_name == 'pull_request' || | |
| (github.event_name == 'issue_comment' && github.event.issue.pull_request) | |
| # Only run for pull requests or comments on pull requests that request Gemini review | |
| if: | | |
| github.event_name == 'pull_request' || | |
| ( | |
| github.event_name == 'issue_comment' && | |
| github.event.issue.pull_request && | |
| contains(github.event.comment.body, '/gemini-review') | |
| ) |
| } else if (GITHUB_EVENT_NAME === 'issue_comment') { | ||
| if (!event.issue.pull_request) return; | ||
| prNumber = event.issue.number; | ||
| const commentBody = event.comment.body || ''; | ||
| if (commentBody.includes('/gemini-review')) { | ||
| triggerSource = 'Comment: /gemini-review'; | ||
| } else { | ||
| return; | ||
| } |
There was a problem hiding this comment.
Security/abuse risk: the issue_comment trigger runs with repository secrets, and currently anyone who can comment on a PR can trigger /gemini-review (including external contributors on fork PRs). Add an authorization check (e.g., event.comment.author_association in {MEMBER, OWNER, COLLABORATOR} or explicitly allow-listed users) before calling Gemini to prevent secret-backed API usage being triggered by untrusted actors.



Summary
This adds a simple Gemini-based review workflow for pull requests.
It gives quick, automated feedback on code changes to help spot potential issues or improvements. The feedback is advisory only and does not block merging.
What it does
/gemini-reviewNotes