- Posted on
- • Artificial Intelligence
Artificial Intelligence Pull Request Reviews
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
AI-Powered Pull Request Reviews from Your Bash Terminal
If your team’s pull requests pile up faster than they get reviewed, you’re not alone. PR backlogs slow shipping, frustrate contributors, and let sneaky bugs slip through. What if you could get fast, consistent, policy-aware feedback on every PR—right from Bash—without replacing human reviewers?
This post shows how to wire up a simple, auditable AI review pipeline in shell. You’ll fetch PR context with the GitHub CLI, send a targeted prompt to a model (cloud or local), parse the result, and plug it into your workflow. You get faster triage, fewer nitpicks, and more time for meaningful human feedback.
Why AI PR reviews are worth it
Speed and coverage: AI highlights obvious issues (security footguns, style drift, missing tests) so humans can focus on architecture and intent.
Consistency: Encodes your team’s policies and style guides so every PR gets the same baseline checks.
Developer ergonomics: Instant feedback from the terminal or CI keeps contributors moving.
Works with what you have: Bash + curl + jq + gh = low-friction, reviewable, version-controlled automation.
What you’ll build
A Bash script that:
- Gathers PR diff and metadata with
ghandjq - Prompts an AI model to analyze changes and return structured findings
- Prints a clean, actionable summary or posts it back to the PR
- Gathers PR diff and metadata with
Integration options:
- Local runs while you iterate
- CI runs on every PR
Prerequisites and installation
We’ll use these tools:
git: repo operations
gh: GitHub CLI for PR context
curl: HTTP requests
jq: JSON processing
Install them with your distro’s package manager.
Apt (Debian/Ubuntu and derivatives):
sudo apt update
sudo apt install -y git curl jq
# Install GitHub CLI (official repo)
sudo mkdir -p -m 755 /etc/apt/keyrings
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/etc/apt/keyrings/githubcli-archive-keyring.gpg
sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
| sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt update
sudo apt install -y gh
Dnf (Fedora, RHEL 9+, CentOS Stream):
sudo dnf install -y git curl jq gh
Zypper (openSUSE Leap/Tumbleweed):
sudo zypper refresh
sudo zypper install -y git curl jq gh
Authenticate gh:
gh auth login
# or non-interactive:
# echo "$GH_TOKEN" | gh auth login --with-token
If you’ll use a cloud model, set your API key (example: OpenAI):
export OPENAI_API_KEY="sk-...your-token..."
Optional: local model with Ollama:
curl -fsSL https://ollama.com/install.sh | sh
# Example model:
ollama pull llama3.1
Step 1: Capture PR context with gh and jq
You want only the relevant bits: changed files, diff hunks, PR title/description.
#!/usr/bin/env bash
set -euo pipefail
: "${PR_NUMBER:?Usage: PR_NUMBER env var required}"
: "${REPO:?Usage: REPO=owner/name required}"
# Fetch a concise diff, skipping noisy files
FILTER='( .filename | test("\\.(md|txt|png|jpg|lock|min\\.js)$") | not )'
TMPDIR="$(mktemp -d)"
trap 'rm -rf "$TMPDIR"' EXIT
# PR metadata
gh api repos/:owner/:repo/pulls/"$PR_NUMBER" \
--repo "$REPO" > "$TMPDIR/pr.json"
# Files and patches
gh api repos/:owner/:repo/pulls/"$PR_NUMBER"/files \
--repo "$REPO" > "$TMPDIR/files.json"
# Build a trimmed review payload
jq --argjson files "$(jq '[ .[] | {filename, status, patch} ]' < "$TMPDIR/files.json")" '
{
number: .number,
title: .title,
author: .user.login,
branch: .head.ref,
base: .base.ref,
body: .body,
files: $files
}' < "$TMPDIR/pr.json" \
| jq '.files |= map(select(.patch != null)) | .files |= map(select('"$FILTER"'))' \
> "$TMPDIR/payload.json"
echo "Prepared PR payload at $TMPDIR/payload.json"
jq '.files | length as $n | "Files to review: \($n)"' "$TMPDIR/payload.json" -r
Run it:
PR_NUMBER=123 REPO=owner/repo ./gather.sh
Step 2: Write a strong prompt (policy + structure)
Good prompts make good reviews. Encode your style rules, security policies, and desired output shape.
Create review_prompt.txt:
You are a senior code reviewer. Task:
- Review ONLY the provided diff patches; avoid speculation beyond context.
- Focus on: security pitfalls, correctness, test coverage, performance hotspots, readability, and policy/style compliance.
- Be concise and actionable.
Return strict JSON with keys:
{
"severity": "none|low|medium|high|critical",
"summary": "one-paragraph overview",
"findings": [
{
"title": "short title",
"severity": "low|medium|high|critical",
"file": "path",
"line": number | null,
"category": "security|bug|style|test|perf|docs|other",
"details": "what and why",
"suggestion": "concrete fix or snippet"
}
],
"metrics": {
"files_inspected": number,
"lines_changed": number
}
}
Policies:
- No secrets/hardcoded creds. Flag .env, API keys, tokens.
- Shell: quote variables; use set -euo pipefail; check return codes.
- Python: prefer pathlib; avoid bare except; add tests for new logic.
- Go: check errors; avoid panics; ensure context usage in I/O.
- JS/TS: sanitize inputs; avoid eval; consistent linting.
If nothing significant, set "severity":"none" and provide 0 findings.
Step 3: Call the model from Bash
Option A: Cloud model (OpenAI-compatible Chat Completions). Save as ai-pr-review.sh:
#!/usr/bin/env bash
set -euo pipefail
: "${PR_NUMBER:?}"
: "${REPO:?}"
: "${OPENAI_API_KEY:?Set OPENAI_API_KEY}"
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
# 1) Build context (reuse Step 1 inline for brevity)
gh api repos/:owner/:repo/pulls/"$PR_NUMBER" --repo "$REPO" > "$WORK/pr.json"
gh api repos/:owner/:repo/pulls/"$PR_NUMBER"/files --repo "$REPO" > "$WORK/files.json"
jq --argjson files "$(jq '[ .[] | {filename, status, patch} ]' < "$WORK/files.json")" '
{
number: .number, title: .title, author: .user.login,
branch: .head.ref, base: .base.ref, body: .body, files: $files
}' < "$WORK/pr.json" \
| jq '.files |= map(select(.patch != null))' > "$WORK/payload.json"
# 2) Build prompt
PROMPT="$(cat review_prompt.txt)"
CONTENT="$(jq -c '.' < "$WORK/payload.json")"
# 3) Call model
MODEL="${MODEL:-gpt-4o-mini}" # adjust as needed
RESPONSE="$(curl -sS https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer ${OPENAI_API_KEY}" \
-H "Content-Type: application/json" \
-d @- <<EOF
{
"model": "${MODEL}",
"temperature": 0,
"response_format": { "type": "json_object" },
"messages": [
{ "role": "system", "content": ${PROMPT@Q} },
{ "role": "user", "content": ${CONTENT@Q} }
]
}
EOF
)"
# 4) Extract JSON result
RESULT="$(echo "$RESPONSE" | jq -r '.choices[0].message.content')"
# Validate JSON
echo "$RESULT" | jq . > "$WORK/review.json"
# 5) Pretty print summary
echo "=== AI Review Summary ==="
jq -r '.summary' "$WORK/review.json"
echo
echo "=== Findings ==="
jq -r '.findings[]? | "- [\(.severity)] \(.title) (\(.file):\(.line//"n/a"))\n Category: \(.category)\n Details: \(.details)\n Suggestion: \(.suggestion)\n"' "$WORK/review.json"
# Optional: post comment to PR
if [[ "${POST_TO_PR:-0}" == "1" ]]; then
{
echo "AI Review:"
echo
echo "Severity: $(jq -r '.severity' "$WORK/review.json")"
echo
echo "Summary:"
jq -r '.summary' "$WORK/review.json"
echo
echo "Findings:"
jq -r '.findings[]? | "* [\(.severity)] \(.title) — \(.file):\(.line//"n/a")\n - \(.details)\n - Suggestion: \(.suggestion)"' "$WORK/review.json"
} > "$WORK/comment.md"
gh pr comment "$PR_NUMBER" --repo "$REPO" --body-file "$WORK/comment.md"
fi
Run it:
PR_NUMBER=123 REPO=owner/repo ./ai-pr-review.sh
# or post to PR:
POST_TO_PR=1 PR_NUMBER=123 REPO=owner/repo ./ai-pr-review.sh
Option B: Local model via Ollama (text response). Quick demo:
PROMPT="$(cat review_prompt.txt)"
CONTEXT="$(cat "$TMPDIR/payload.json")"
printf "%s\n\n%s\n" "$PROMPT" "$CONTEXT" | ollama run llama3.1
Tip: Ask the model to return strict JSON (as in the prompt) and pipe into jq to validate.
Step 4: Put reviewers in CI and local workflows
- GitHub Actions (runs on every PR):
name: AI PR Review
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
review:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Install deps
run: |
sudo apt-get update
sudo apt-get install -y jq curl
- name: AI Review
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
PR_NUMBER=${{ github.event.pull_request.number }} \
REPO=${{ github.repository }} \
POST_TO_PR=1 \
bash ./ai-pr-review.sh
- Pre-commit or pre-push (fast local feedback):
# .git/hooks/pre-push
#!/usr/bin/env bash
set -euo pipefail
branch="$(git rev-parse --abbrev-ref HEAD)"
if [[ "$branch" =~ ^(main|master)$ ]]; then
exit 0
fi
# Only run if diff is not huge
changes="$(git diff --staged | wc -l)"
if (( changes < 2000 )); then
echo "Running AI quick review on staged changes..."
# Use a lightweight local model or short cloud call
# Adapt gather logic to use staged diff instead of PR
fi
Step 5: Make it safe, fast, and useful
Privacy and compliance:
- Don’t send sensitive code to external APIs unless permitted. Prefer local models or enterprise endpoints.
- Redact secrets before sending. Example:
sed -E 's/(api[_-]?key|token|passwd|password)\s*=\s*[^[:space:]]+/REDACTED=/gi'Scope the context:
- Filter out generated/vendor/lock files; cap diff size or chunk by file.
- Keep the prompt short and specific to reduce cost and latency.
Deterministic, parseable output:
- Ask for strict JSON and validate with
jq. On failure, retry once with a “return valid JSON” instruction.
- Ask for strict JSON and validate with
Cost and rate limits:
- Use small, capable models for triage; escalate to bigger models only if severity >= medium.
Keep humans in the loop:
- Post comments as suggestions, not blockers.
- Auto-apply labels based on severity:
severity="$(jq -r '.severity' review.json)" case "$severity" in high|critical) gh pr edit "$PR_NUMBER" --add-label "ai:needs-attention" --repo "$REPO" ;; none|low) gh pr edit "$PR_NUMBER" --add-label "ai:clean" --repo "$REPO" ;; esac
Real-world wins
Security catch: AI flagged a newly added
.envwith a probable token. The team rotated credentials and updated.gitignore.Reliability: Found unchecked command failures in a Bash deploy script; adding
set -euo pipefailand exit-code checks prevented silent partial deploys.Tests: Identified a new code path without coverage; author added a focused unit test, catching a boundary bug.
Conclusion and next steps
AI PR reviews won’t replace your best reviewers—but they will unblock them. Start small: run the script locally on a couple of PRs, tune the prompt to your style, then wire it into CI. Measure time-to-first-feedback and defect rates; iterate on filters and policies.
Next steps:
Drop the scripts into your repo and try a live PR.
Add team-specific rules to
review_prompt.txt.Enable CI posting and labeling for triage.
If your code is sensitive, start with a local model and compare results.
Have questions or want a hardened version for your stack? Try the scripts above, then open an issue or share your experience. Happy reviewing—right from Bash!