Automating Code Reviews with Gemini CLI: A Step-by-Step Pipeline
Build an automated code review pipeline using Gemini CLI. Includes GitHub Actions integration, pre-commit hooks, custom review templates, and CI/CD patterns.
Zhihao MuIntroduction
Code review is one of the highest-leverage activities in a software team's workflow. A good review catches bugs before they reach production, enforces architectural consistency, and transfers knowledge between team members. It is also one of the most expensive activities, measured in engineer-hours. A non-trivial pull request can demand 30–90 minutes of focused attention from a senior engineer. Across a team of ten shipping multiple PRs per day, that adds up fast.
The uncomfortable truth most teams avoid discussing is that not all code review time is spent well. A significant portion goes to mechanical concerns: checking whether function names follow the project's naming convention, verifying that all error paths are handled, confirming that new functions have tests, spotting copy-paste mistakes, flagging potential null dereferences. These are valuable checks, but they do not require senior engineering judgment. They require pattern matching — exactly what language models do well.
Gemini CLI is a terminal-native interface to Google's Gemini models with a large context window (over one million tokens in Gemini 1.5 Pro) and deep integration with the filesystem. These properties make it well-suited to code review automation: you can pipe diffs directly into it, feed it entire files for deeper analysis, and embed it into your existing Git and CI/CD workflows with minimal tooling overhead. This article walks through building a complete automated code review pipeline — from a simple one-liner you can run in 30 seconds to a full GitHub Actions workflow that comments directly on pull requests.
TL;DR
- Gemini CLI can review code from stdin, diffs, or multi-file sets using straightforward shell commands.
- A reusable prompt template in a
REVIEW_TEMPLATE.mdfile gives you consistent, project-specific review output. - A pre-commit hook runs Gemini CLI on staged changes before every commit, catching issues locally before they ever reach CI.
- A GitHub Actions workflow runs reviews on every pull request and posts the findings as a PR comment automatically.
- Custom rule files let you encode project-specific standards — naming conventions, security requirements, architecture constraints — that the model checks on every review.
- The goal is not to replace human reviewers. It is to handle the mechanical checklist so human reviewers can focus on design, architecture, and domain correctness.
Why Automate Code Reviews
The Pain Points of Manual Review
Manual code review is valuable and should remain a core practice. But it has structural weaknesses that compound as teams scale.
Reviewer fatigue and inconsistency. A reviewer who is deep in their own work context, reviewing a PR at the end of the day, will not catch the same issues as the same reviewer on a fresh morning session. Mechanical errors slip through inconsistently — not because reviewers are careless, but because sustained attention is a finite resource. The tenth file in a large PR gets substantially less scrutiny than the first.
Review bottlenecks blocking delivery. In many teams, the same two or three senior engineers are the designated reviewers for all significant code. When they are in meetings, on vacation, or deep in their own sprint work, PRs queue up. This bottleneck slows delivery and creates pressure to merge under-reviewed code when the queue gets long enough.
The politeness problem. Human reviewers moderate their feedback based on social dynamics. Junior engineers hesitate to leave critical comments on a PR from a senior. New team members avoid flagging things they are not sure about. The result is a systematic bias toward soft feedback on high-status code, regardless of its quality.
Inconsistent standards over time. A coding standard documented in a CONTRIBUTING.md file three years ago may be consistently enforced by engineers who were there when it was written and completely unknown to engineers who joined recently. Standards drift as teams change.
The Value of AI-Assisted Review
Automated review with Gemini CLI addresses these structural weaknesses directly.
It is tireless and consistent. The model applies the same attention to the tenth file that it applies to the first. It does not get tired, does not modulate feedback based on who wrote the code, and does not have off days.
It is fast. A diff that would take a human reviewer 20 minutes to read gets a first-pass review in under 10 seconds. Engineers get feedback before they have even opened their laptop for the day.
It scales to the team's velocity. Whether you are shipping 5 PRs per week or 50, the automated pipeline handles them all with the same throughput.
It enforces rules without social friction. The model will flag every instance of a naming convention violation regardless of who wrote the code. This is not rudeness — it is consistency.
The key framing is that automated review handles the checklist so that human reviewers can focus on what only humans can do well: evaluating architectural decisions, understanding whether the code solves the right problem, assessing long-term maintainability from domain experience, and mentoring less experienced engineers.
Basic Code Review Commands
Before building pipelines, it is worth understanding the basic primitives. These commands work anywhere you have Gemini CLI installed.
Reviewing Code from Stdin
The simplest form of review is piping a file directly into Gemini CLI:
cat src/services/PaymentService.ts | gemini -p "Review this code for bugs, security issues, and style problems. Be specific about line numbers."
This is useful for quick ad-hoc reviews during development. You can also combine multiple files:
cat src/services/PaymentService.ts src/types/payment.ts | gemini -p "Review these two files together. Focus on whether the types are correctly used in the service."
Reviewing a Git Diff
Reviewing the diff of uncommitted changes is the most common use case during active development:
git diff | gemini -p "Review this diff. Identify bugs, missing error handling, security vulnerabilities, and any deviations from clean code principles."
To review all staged changes before committing:
git diff --cached | gemini -p "Review these staged changes. Check for: (1) bugs and logic errors, (2) missing input validation, (3) unhandled error paths, (4) any obvious security issues."
To review the diff between your branch and main:
git diff main...HEAD | gemini -p "Review this pull request diff. Summarize the changes first, then provide detailed feedback on any issues found."
Reviewing Multiple Files
For reviewing a module or feature set across several files, use cat with a glob or list files explicitly:
cat src/auth/*.ts | gemini -p "Review the authentication module for security vulnerabilities. Pay special attention to token handling, session management, and input validation."
For a more structured multi-file review that preserves filename context:
for f in src/auth/*.ts; do echo "=== $f ==="; cat "$f"; done | gemini -p "Review each file in this authentication module. For each file, list issues found. Be explicit about which file each issue belongs to."
Building a Review Template
Ad-hoc prompts work for quick checks, but for consistent, repeatable reviews you need a template. A good review template defines exactly what the model should check, in what order, and how to format its output.
Create a file called REVIEW_TEMPLATE.md at the root of your project:
# Code Review Instructions
You are conducting a thorough code review. Analyze the provided code or diff and produce a structured review covering the categories below.
## Review Categories
### 1. Critical Issues (must fix before merge)
- Bugs and logic errors that would cause incorrect behavior
- Security vulnerabilities (injection, authentication bypass, sensitive data exposure, etc.)
- Unhandled exceptions or error paths that could cause crashes
- Race conditions or concurrency issues
- Data integrity problems
### 2. Important Issues (should fix before merge)
- Missing input validation on public functions and API boundaries
- Functions doing too much (violating single responsibility)
- Missing or incorrect error messages and logging
- Hardcoded values that should be configuration
- Missing tests for new functionality
### 3. Minor Issues (can fix in a follow-up)
- Naming conventions that deviate from project standards
- Code duplication that could be extracted into a shared utility
- Overly complex logic that could be simplified
- Missing or outdated comments and documentation
### 4. Positive Observations
- Patterns or decisions that are done particularly well
- Good test coverage
- Clear and well-structured code
## Output Format
Produce your review in this exact structure:
**Summary:** [1-2 sentences describing the change and its overall quality]
**Critical Issues:**
- [CRITICAL] `filename:line` — Description of the issue. Suggested fix.
**Important Issues:**
- [IMPORTANT] `filename:line` — Description of the issue. Suggested fix.
**Minor Issues:**
- [MINOR] `filename:line` — Description of the issue. Suggested fix.
**Positive Notes:**
- [GOOD] Description of what is done well.
**Verdict:** APPROVE / REQUEST_CHANGES / NEEDS_DISCUSSION
If no issues exist in a category, write "None found." Do not omit categories.
Use this template in your review commands:
git diff --cached | gemini -p "$(cat REVIEW_TEMPLATE.md)"$'\n\n'"Review the following diff:"$'\n'"$(git diff --cached)"
Or more cleanly, create a shell function in your ~/.zshrc or ~/.bashrc:
# Add to ~/.zshrc or ~/.bashrc
review() {
local template_path="${REVIEW_TEMPLATE:-$PWD/REVIEW_TEMPLATE.md}"
local diff_content
if [ -n "$1" ]; then
# Review a specific file
diff_content=$(cat "$1")
else
# Review staged changes
diff_content=$(git diff --cached)
fi
if [ -z "$diff_content" ]; then
echo "No changes to review. Stage some changes with git add first."
return 1
fi
if [ -f "$template_path" ]; then
prompt="$(cat "$template_path")"$'\n\n'"Review the following code:\n\n${diff_content}"
else
prompt="Review this code for bugs, security issues, and code quality problems:\n\n${diff_content}"
fi
echo "$prompt" | gemini
}
After sourcing your shell config, you can run review from any project directory with a REVIEW_TEMPLATE.md to get a structured review of your staged changes.
Pre-Commit Hook Integration
The most impactful place to run automated reviews is before the commit — when the engineer is still context-loaded on the change and can fix issues immediately. A pre-commit hook that triggers Gemini CLI takes about 10 seconds and catches the majority of mechanical issues before they ever leave the developer's machine.
Create the following file at .git/hooks/pre-commit (or use the script below to install it):
#!/usr/bin/env bash
# .git/hooks/pre-commit
# Runs Gemini CLI code review on staged changes before commit.
# Exit code 0 = allow commit, exit code 1 = block commit.
set -euo pipefail
# --- Configuration ---
REVIEW_TEMPLATE="${REVIEW_TEMPLATE_PATH:-$(git rev-parse --show-toplevel)/REVIEW_TEMPLATE.md}"
GEMINI_CMD="${GEMINI_CMD:-gemini}"
MIN_DIFF_LINES="${MIN_DIFF_LINES:-5}" # Skip review for tiny changes
BLOCK_ON_CRITICAL="${BLOCK_ON_CRITICAL:-true}" # Block commit if CRITICAL issues found
# --- Check Dependencies ---
if ! command -v "$GEMINI_CMD" &>/dev/null; then
echo "[pre-commit] gemini CLI not found. Skipping AI review."
echo "[pre-commit] Install with: npm install -g @google/generative-ai-cli"
exit 0
fi
# --- Get Staged Diff ---
DIFF=$(git diff --cached --unified=5)
if [ -z "$DIFF" ]; then
exit 0
fi
DIFF_LINE_COUNT=$(echo "$DIFF" | wc -l | tr -d ' ')
if [ "$DIFF_LINE_COUNT" -lt "$MIN_DIFF_LINES" ]; then
echo "[pre-commit] Diff too small ($DIFF_LINE_COUNT lines). Skipping AI review."
exit 0
fi
# --- Build Prompt ---
if [ -f "$REVIEW_TEMPLATE" ]; then
PROMPT="$(cat "$REVIEW_TEMPLATE")"$'\n\n'"Review the following staged diff:\n\n\`\`\`diff\n${DIFF}\n\`\`\`"
else
PROMPT="Review this staged diff for bugs, security vulnerabilities, and code quality issues. Flag any CRITICAL issues that should block this commit:\n\n\`\`\`diff\n${DIFF}\n\`\`\`"
fi
# --- Run Review ---
echo ""
echo "Running AI code review on staged changes..."
echo "----------------------------------------------"
REVIEW_OUTPUT=$(echo "$PROMPT" | "$GEMINI_CMD" 2>&1)
REVIEW_EXIT=$?
if [ "$REVIEW_EXIT" -ne 0 ]; then
echo "[pre-commit] Gemini CLI returned error. Skipping AI review."
echo "[pre-commit] Error: $REVIEW_OUTPUT"
exit 0
fi
echo "$REVIEW_OUTPUT"
echo "----------------------------------------------"
echo ""
# --- Check for Critical Issues ---
if [ "$BLOCK_ON_CRITICAL" = "true" ]; then
if echo "$REVIEW_OUTPUT" | grep -q "\[CRITICAL\]"; then
echo "[pre-commit] CRITICAL issues found. Commit blocked."
echo "[pre-commit] Fix the issues above or use BLOCK_ON_CRITICAL=false to bypass."
echo ""
exit 1
fi
fi
# --- Allow Commit ---
echo "[pre-commit] AI review complete. No blocking issues found."
exit 0
Make the hook executable and install it:
# Make the hook executable
chmod +x .git/hooks/pre-commit
# Or use this installer script to set it up for the whole team via a package.json script
cat > scripts/install-hooks.sh << 'INSTALL_EOF'
#!/usr/bin/env bash
set -e
HOOKS_DIR="$(git rev-parse --git-dir)/hooks"
cp scripts/pre-commit "$HOOKS_DIR/pre-commit"
chmod +x "$HOOKS_DIR/pre-commit"
echo "Git hooks installed successfully."
INSTALL_EOF
chmod +x scripts/install-hooks.sh
Add the installation step to your package.json so it runs automatically after npm install:
{
"scripts": {
"prepare": "bash scripts/install-hooks.sh"
}
}
Now every developer on the team gets AI-assisted pre-commit review as soon as they install the project's dependencies.
GitHub Actions Pipeline
The pre-commit hook catches issues locally, but you also want a review that runs in CI — both as a safety net for developers who skip the local hook and to produce a persistent record of review feedback on each pull request.
The following workflow runs a Gemini CLI review on every pull request and posts the result as a PR comment:
# .github/workflows/ai-code-review.yml
name: AI Code Review
on:
pull_request:
types: [opened, synchronize, reopened]
# Only run when source code changes, not docs or config
paths:
- "src/**"
- "lib/**"
- "app/**"
- "packages/**"
permissions:
pull-requests: write
contents: read
jobs:
review:
name: Gemini CLI Review
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history needed for accurate diffs
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install Gemini CLI
run: npm install -g @google/generative-ai-cli
- name: Generate PR diff
id: diff
run: |
git fetch origin ${{ github.base_ref }}
DIFF=$(git diff origin/${{ github.base_ref }}...HEAD -- \
'*.ts' '*.tsx' '*.js' '*.jsx' '*.py' '*.go' '*.rs' \
':!*.min.js' ':!*.lock' ':!dist/**' ':!build/**')
# Write diff to file to avoid argument length limits
echo "$DIFF" > /tmp/pr_diff.txt
# Count changed lines for logging
LINES=$(wc -l < /tmp/pr_diff.txt)
echo "diff_lines=$LINES" >> "$GITHUB_OUTPUT"
echo "Diff size: $LINES lines"
- name: Check diff size
id: check_size
run: |
LINES=${{ steps.diff.outputs.diff_lines }}
if [ "$LINES" -lt 10 ]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "Diff too small ($LINES lines). Skipping review."
elif [ "$LINES" -gt 5000 ]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "Diff too large ($LINES lines). Skipping automated review for oversized PRs."
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
- name: Run Gemini CLI review
id: review
if: steps.check_size.outputs.skip == 'false'
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
run: |
# Build the review prompt
TEMPLATE_PATH="REVIEW_TEMPLATE.md"
if [ -f "$TEMPLATE_PATH" ]; then
PROMPT_HEADER=$(cat "$TEMPLATE_PATH")
else
PROMPT_HEADER="You are reviewing a pull request. Analyze the diff below for bugs, security vulnerabilities, missing error handling, and code quality issues. Format your output with clear sections: Critical Issues, Important Issues, Minor Issues, and Positive Notes."
fi
# Combine prompt and diff
{
echo "$PROMPT_HEADER"
echo ""
echo "## Pull Request Diff"
echo ""
echo '```diff'
cat /tmp/pr_diff.txt
echo '```'
} > /tmp/full_prompt.txt
# Run the review
gemini < /tmp/full_prompt.txt > /tmp/review_output.txt 2>&1
REVIEW_EXIT=$?
if [ $REVIEW_EXIT -ne 0 ]; then
echo "Gemini CLI error:"
cat /tmp/review_output.txt
exit 1
fi
# Check for critical issues (used in next step)
if grep -q "\[CRITICAL\]" /tmp/review_output.txt; then
echo "has_critical=true" >> "$GITHUB_OUTPUT"
else
echo "has_critical=false" >> "$GITHUB_OUTPUT"
fi
echo "Review completed successfully."
- name: Post review comment
if: steps.check_size.outputs.skip == 'false'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');
let reviewBody;
try {
reviewBody = fs.readFileSync('/tmp/review_output.txt', 'utf8');
} catch (e) {
reviewBody = 'AI review could not be generated. Check the workflow logs for details.';
}
const hasCritical = '${{ steps.review.outputs.has_critical }}' === 'true';
const header = hasCritical
? '## AI Code Review — Critical Issues Found\n\n> **This review identified CRITICAL issues. Please address them before merging.**\n\n'
: '## AI Code Review\n\n';
const footer = '\n\n---\n*Generated by Gemini CLI. This is an automated first-pass review. Human review is still required.*';
const body = header + reviewBody + footer;
// Find and update existing AI review comment, or create a new one
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existingComment = comments.find(c =>
c.user.type === 'Bot' && c.body.includes('AI Code Review')
);
if (existingComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}
- name: Fail on critical issues
if: steps.review.outputs.has_critical == 'true'
run: |
echo "::error::AI review found CRITICAL issues in this PR. See the PR comment for details."
exit 1
Add your Gemini API key to the repository's secrets (Settings > Secrets and variables > Actions > New repository secret, name it GEMINI_API_KEY).
The workflow does several things worth calling out: it limits the diff to source code files only (excluding lock files, build artifacts, and minified files), it skips reviews for diffs that are either too small to warrant analysis or so large that the review would be unfocused, it upserts the review comment on subsequent pushes to the same PR rather than accumulating duplicate comments, and it fails the CI check when critical issues are found.
Custom Review Rules
Generic review prompts produce generic feedback. The real power of this pipeline comes from encoding your project's specific standards into the review instructions. A file called .gemini/review-rules.md at the project root is a natural place to maintain these rules:
# Project-Specific Review Rules
## Naming Conventions
- React components: PascalCase, exported as named exports (never default exports)
- Hooks: camelCase prefixed with `use` (e.g., `useAuthState`, not `authStateHook`)
- Event handlers: prefixed with `handle` (e.g., `handleSubmit`, not `onSubmit` or `submitHandler`)
- Constants: SCREAMING_SNAKE_CASE for module-level constants, camelCase for function-scoped constants
- Test files: `*.test.ts` or `*.spec.ts` co-located with the source file, never in a separate `__tests__` directory
## Architecture Rules
- Business logic must never live in React components. Extract to custom hooks or service functions.
- API calls must go through the `src/api/` client layer. No direct `fetch` calls in components or hooks.
- State management: local state for UI concerns (Zustand store for shared state). No prop drilling beyond two levels.
- All database access must go through repository functions in `src/repositories/`. No raw SQL or ORM calls outside repositories.
## Security Requirements
- All user-supplied values rendered in JSX must pass through the `sanitize()` utility from `src/utils/security.ts`.
- API keys and secrets must never appear in source code. Use environment variables accessed only through `src/config/env.ts`.
- All API routes must use the `authenticate` middleware unless explicitly marked with a `// PUBLIC ROUTE` comment and approved in `docs/public-routes.md`.
- File uploads must be validated with the `validateUpload()` function before processing.
## Error Handling
- All async functions must have try/catch blocks with typed error handling.
- User-facing error messages must come from the `src/constants/errors.ts` catalog, never be written inline.
- All errors must be logged using `logger.error()` from `src/utils/logger.ts` before being re-thrown or transformed.
- Network errors must be retried using the `withRetry()` utility with exponential backoff.
## Testing Requirements
- Every new exported function must have at least one unit test covering the happy path.
- Every new API route must have an integration test covering authentication, success, and failure cases.
- Test descriptions must follow the pattern: `it("should [expected behavior] when [condition]")`
- No test should have side effects that persist after the test completes (use `beforeEach`/`afterEach` cleanup).
## Performance
- Components that render lists must use `React.memo` and provide stable `key` props derived from entity IDs.
- Expensive computations in components must use `useMemo` with documented dependency arrays.
- Images must use the `<OptimizedImage>` component from `src/components/ui/` rather than raw `<img>` tags.
- Database queries fetching more than 100 rows must use cursor-based pagination via the `paginate()` helper.
Update your REVIEW_TEMPLATE.md to include these rules:
# Code Review Instructions
You are conducting a thorough code review for this project.
## Project-Specific Rules
{{RULES}}
## Review Categories
[... same categories as before ...]
And update your review script to inject the rules:
#!/usr/bin/env bash
# scripts/run-review.sh
RULES_FILE="${RULES_FILE:-$(git rev-parse --show-toplevel)/.gemini/review-rules.md}"
TEMPLATE_FILE="${TEMPLATE_FILE:-$(git rev-parse --show-toplevel)/REVIEW_TEMPLATE.md}"
DIFF=$(git diff --cached)
if [ -z "$DIFF" ]; then
DIFF=$(git diff HEAD~1..HEAD)
fi
RULES=""
if [ -f "$RULES_FILE" ]; then
RULES=$(cat "$RULES_FILE")
fi
TEMPLATE=""
if [ -f "$TEMPLATE_FILE" ]; then
TEMPLATE=$(cat "$TEMPLATE_FILE")
# Inject rules into template
TEMPLATE="${TEMPLATE/\{\{RULES\}\}/$RULES}"
else
TEMPLATE="Review this code against the following project rules:\n\n${RULES}"
fi
PROMPT="${TEMPLATE}"$'\n\n'"## Code to Review\n\n\`\`\`diff\n${DIFF}\n\`\`\`"
echo "$PROMPT" | gemini
Handling Review Output
Getting a review from Gemini CLI is step one. In a production pipeline, you also need to parse the output programmatically — to count issues by severity, to extract actionable items, and to decide whether to block a merge.
Here is a Node.js script that parses the review output and produces a structured JSON summary:
// scripts/parse-review.ts
import { readFileSync } from "fs";
interface ReviewIssue {
severity: "CRITICAL" | "IMPORTANT" | "MINOR" | "GOOD";
location?: string;
description: string;
}
interface ReviewSummary {
verdict: "APPROVE" | "REQUEST_CHANGES" | "NEEDS_DISCUSSION" | "UNKNOWN";
summary: string;
issues: ReviewIssue[];
counts: {
critical: number;
important: number;
minor: number;
positive: number;
};
rawOutput: string;
}
function parseReviewOutput(raw: string): ReviewSummary {
const issues: ReviewIssue[] = [];
// Parse individual issue lines
const issuePattern =
/\[(CRITICAL|IMPORTANT|MINOR|GOOD)\]\s+(?:`([^`]+)`\s+—\s+)?(.+)/g;
let match: RegExpExecArray | null;
while ((match = issuePattern.exec(raw)) !== null) {
const [, severity, location, description] = match;
issues.push({
severity: severity as ReviewIssue["severity"],
location: location?.trim(),
description: description.trim(),
});
}
// Extract verdict
const verdictMatch = raw.match(
/\*\*Verdict:\*\*\s*(APPROVE|REQUEST_CHANGES|NEEDS_DISCUSSION)/
);
const verdict = (verdictMatch?.[1] ?? "UNKNOWN") as ReviewSummary["verdict"];
// Extract summary
const summaryMatch = raw.match(/\*\*Summary:\*\*\s*(.+?)(?:\n|$)/);
const summary = summaryMatch?.[1]?.trim() ?? "";
const counts = {
critical: issues.filter((i) => i.severity === "CRITICAL").length,
important: issues.filter((i) => i.severity === "IMPORTANT").length,
minor: issues.filter((i) => i.severity === "MINOR").length,
positive: issues.filter((i) => i.severity === "GOOD").length,
};
return { verdict, summary, issues, counts, rawOutput: raw };
}
function formatAsGitHubAnnotations(summary: ReviewSummary): void {
// Emit GitHub Actions workflow commands for annotations
for (const issue of summary.issues) {
const location = issue.location ? ` (${issue.location})` : "";
const message = `${issue.description}${location}`;
switch (issue.severity) {
case "CRITICAL":
console.log(`::error::${message}`);
break;
case "IMPORTANT":
console.log(`::warning::${message}`);
break;
case "MINOR":
console.log(`::notice::${message}`);
break;
}
}
}
// Main
const inputFile = process.argv[2];
const raw = inputFile
? readFileSync(inputFile, "utf8")
: readFileSync("/dev/stdin", "utf8");
const summary = parseReviewOutput(raw);
if (process.env.GITHUB_ACTIONS) {
formatAsGitHubAnnotations(summary);
}
console.log(JSON.stringify(summary, null, 2));
// Exit with non-zero code if critical issues found
process.exit(summary.counts.critical > 0 ? 1 : 0);
Integrate this into your CI workflow by piping the review output through the parser:
- name: Run Gemini CLI review and parse output
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
run: |
gemini < /tmp/full_prompt.txt > /tmp/review_output.txt 2>&1
npx ts-node scripts/parse-review.ts /tmp/review_output.txt > /tmp/review_summary.json
echo "Review summary:"
cat /tmp/review_summary.json | jq '{ verdict, counts }'
The GitHub Actions annotations (::error::, ::warning::, ::notice::) produced by this script appear directly in the PR's "Files changed" view, making critical issues visible inline rather than only in a comment.
Best Practices
Building an effective automated code review pipeline requires more than just wiring up the commands. Here are the patterns that separate useful automation from noise.
1. Be explicit about what you want checked. Generic prompts produce generic output. The more specifically you define the review criteria — naming conventions, architecture rules, security requirements — the more actionable the output will be. A rule like "all async functions must have try/catch blocks" produces a precise, actionable finding. "Check for error handling" produces a vague observation.
2. Keep the scope focused. A review covering 5,000 lines of diff will be less precise than a review covering 200 lines. Where possible, encourage smaller, focused PRs. If large PRs are unavoidable, consider breaking the diff into logical chunks (by directory or feature) and running separate reviews on each chunk rather than feeding the entire diff at once.
3. Never block production deploys on automated review alone. Automated review is a first-pass filter, not a correctness guarantee. A model that misses a critical bug and gives the PR a clean verdict is worse than no automated review, because it creates false confidence. Set expectations clearly: automated review catches mechanical issues; human review is still required for correctness.
4. Tune the blocking threshold carefully. Blocking commits on any CRITICAL finding is appropriate. Blocking on IMPORTANT findings will cause friction without proportional benefit, because IMPORTANT issues — missing tests, non-critical error handling gaps — are often genuinely debatable. Start with blocking only on CRITICAL and adjust based on your team's experience with the output quality.
5. Rotate and version your review templates. Your REVIEW_TEMPLATE.md and .gemini/review-rules.md are living documents. When a new architecture pattern is adopted, add a rule. When a rule consistently produces false positives, remove or refine it. Treat these files with the same care you would treat your lint configuration. Version them in Git and discuss changes in code review the same way you would discuss changes to .eslintrc.
6. Log review output for quality tracking. Save the raw output of each automated review — with the PR number, the diff size, and the verdict — to a persistent store (an S3 bucket, a database, or even a Git-committed log file). This creates a dataset you can use to evaluate the model's accuracy over time, identify categories of issues it consistently misses, and measure whether the automated reviews are actually catching things that would have been caught by human reviewers.
7. Give engineers an easy bypass for known false positives. Automated tools inevitably flag some valid code as problematic. Provide a documented way to suppress specific findings — a comment directive like // gemini-review-ignore: RULE_NAME or a .gemini-review-ignore config file — so engineers can silence known false positives without disabling the entire review. This maintains the signal-to-noise ratio and prevents engineers from simply bypassing the entire hook when it becomes too noisy.
FAQ
Q: Will running Gemini CLI on every commit or PR become expensive as the team scales?
For most teams, the cost is negligible relative to the time saved. Gemini 1.5 Flash — the most cost-effective model suitable for code review — charges a fraction of a cent per 1,000 tokens of input. A 200-line diff is roughly 2,000–3,000 tokens. Even at 50 PRs per day, the daily cost is under $1. For teams sensitive to API costs, you can configure the workflow to only run on PRs above a minimum diff size or only on files in specific directories. Set a monthly budget alert in Google Cloud console to ensure there are no surprises.
Q: How do we handle false positives without eroding trust in the tool?
False positives are inevitable with any static analysis tool, AI-powered or otherwise. The key is to treat the automated review as a first draft that a human must confirm, not as a final verdict. When an engineer disagrees with a finding, they should be able to dismiss it with a comment explaining why — both in the PR discussion and, for recurring patterns, by updating the review rules file to explicitly exclude that pattern. Maintaining a log of dismissed findings and reviewing it quarterly helps identify systematic false positives that should be suppressed in the template.
Q: Should the automated review replace peer code review from teammates?
No, and it should not be positioned that way. The automated review handles the mechanical checklist — naming conventions, missing error handling, obvious security anti-patterns. Human peer review addresses things the model cannot: whether the architecture is appropriate for the problem, whether the business logic is correct given domain knowledge the model does not have, whether the change introduces subtle interactions with other parts of the system that require contextual understanding. Position automated review as eliminating the tedious part of code review so that human reviewers can spend their time on the parts that actually require human judgment.
Conclusion
Code review automation with Gemini CLI is not a moonshot. It is a practical engineering investment that pays back quickly: the pre-commit hook takes 30 minutes to set up and will catch issues before they leave a developer's machine for years. The GitHub Actions pipeline takes a few hours to configure and produces a persistent first-pass review on every PR indefinitely. The custom rules file encodes your team's standards in a form that is consistently enforced regardless of who wrote the code or who is reviewing it.
The pipeline described in this article is a starting point, not a final destination. Your review template will evolve as you learn which categories of issues the model catches reliably and which it misses. Your rules file will grow as your codebase matures and your architectural standards solidify. Your CI workflow will be tuned based on what your team actually needs from automated review versus what generates noise.
The constant throughout all of this is the underlying goal: free human reviewers from the checklist so they can focus on the work that only they can do well. That trade-off — model handles the mechanical, humans handle the complex — is where automated code review creates its most durable value.
Related reading:

Zhihao Mu
· Full-stack DeveloperDeveloper and technical writer passionate about AI-powered development tools. Building geminicli.one to help developers unlock the full potential of Gemini CLI.
GitHub ProfileWas this article helpful?