Tools: How to Automate Code Reviews with Local LLMs (No API Keys Required)
Why Local LLMs for Code Review?
What You'll Need
Step 1: Install Ollama
Step 2: Create the Review Script
Step 3: Set Up the Git Hook
Step 4: Test It
Making It Actually Useful
Skip Reviews for Trivial Commits
Focus on Specific File Types
Bypass When Needed
Log Reviews for Later
Performance Notes
What It Won't Catch
The Actual Value I got tired of waiting for PR reviews. My team's across three timezones, and sometimes a simple "is this logic right?" question sits for 12 hours. So I built an automated pre-commit code review using Ollama and git hooks. It runs entirely localβno API keys, no usage limits, no sending proprietary code to external servers. Here's the setup that's been running on my machine for two months. Cloud APIs are great until: Running local LLMs for coding tasks solves all of this. The quality isn't GPT-4, but for catching obvious bugs and suggesting improvements? It's surprisingly good. Pull a coding-focused model. I've tested several; here's what works: Save this as ~/.local/bin/ai-review: Create a pre-commit hook in your repo: Want this globally? Use git templates: Stage some code and commit: You'll see something like: The basic setup works, but here's how I've tuned mine: The 6.7B model catches 80% of what the larger models find. For pre-commit automation, speed matters more than catching edge cases. Be realistic. Local LLMs miss: This isn't a replacement for human review. It's a first pass that catches the embarrassing stuff before your teammates see it. Two months in, here's what I've noticed: The whole setup took 10 minutes. The ROI has been significant. More at dev.to/cumulus Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse
# Linux/WSL
-weight: 500;">curl -fsSL https://ollama.com/-weight: 500;">install.sh | sh # macOS
-weight: 500;">brew -weight: 500;">install ollama # Start the -weight: 500;">service
ollama serve
# Linux/WSL
-weight: 500;">curl -fsSL https://ollama.com/-weight: 500;">install.sh | sh # macOS
-weight: 500;">brew -weight: 500;">install ollama # Start the -weight: 500;">service
ollama serve
# Linux/WSL
-weight: 500;">curl -fsSL https://ollama.com/-weight: 500;">install.sh | sh # macOS
-weight: 500;">brew -weight: 500;">install ollama # Start the -weight: 500;">service
ollama serve
# Best balance of speed and quality
ollama pull deepseek-coder:6.7b # If you have 16GB+ VRAM
ollama pull codellama:13b
# Best balance of speed and quality
ollama pull deepseek-coder:6.7b # If you have 16GB+ VRAM
ollama pull codellama:13b
# Best balance of speed and quality
ollama pull deepseek-coder:6.7b # If you have 16GB+ VRAM
ollama pull codellama:13b
#!/bin/bash
set -e MODEL="${AI_REVIEW_MODEL:-deepseek-coder:6.7b}"
DIFF=$(-weight: 500;">git diff --cached --diff-filter=ACMR) if [ -z "$DIFF" ]; then echo "No staged changes to review" exit 0
fi PROMPT="Review this code diff. Be concise. Flag:
1. Obvious bugs or logic errors
2. Security issues (SQL injection, XSS, hardcoded secrets)
3. Performance problems
4. Missing error handling If the code looks fine, just say 'LGTM'. Diff:
$DIFF" echo "π Running local code review..."
echo "" ollama run "$MODEL" "$PROMPT" 2>/dev/null echo ""
echo "---"
echo "Review complete. Commit? [y/N]"
read -r response
if [[ "$response" =~ ^[Yy]$ ]]; then exit 0
else exit 1
fi
#!/bin/bash
set -e MODEL="${AI_REVIEW_MODEL:-deepseek-coder:6.7b}"
DIFF=$(-weight: 500;">git diff --cached --diff-filter=ACMR) if [ -z "$DIFF" ]; then echo "No staged changes to review" exit 0
fi PROMPT="Review this code diff. Be concise. Flag:
1. Obvious bugs or logic errors
2. Security issues (SQL injection, XSS, hardcoded secrets)
3. Performance problems
4. Missing error handling If the code looks fine, just say 'LGTM'. Diff:
$DIFF" echo "π Running local code review..."
echo "" ollama run "$MODEL" "$PROMPT" 2>/dev/null echo ""
echo "---"
echo "Review complete. Commit? [y/N]"
read -r response
if [[ "$response" =~ ^[Yy]$ ]]; then exit 0
else exit 1
fi
#!/bin/bash
set -e MODEL="${AI_REVIEW_MODEL:-deepseek-coder:6.7b}"
DIFF=$(-weight: 500;">git diff --cached --diff-filter=ACMR) if [ -z "$DIFF" ]; then echo "No staged changes to review" exit 0
fi PROMPT="Review this code diff. Be concise. Flag:
1. Obvious bugs or logic errors
2. Security issues (SQL injection, XSS, hardcoded secrets)
3. Performance problems
4. Missing error handling If the code looks fine, just say 'LGTM'. Diff:
$DIFF" echo "π Running local code review..."
echo "" ollama run "$MODEL" "$PROMPT" 2>/dev/null echo ""
echo "---"
echo "Review complete. Commit? [y/N]"
read -r response
if [[ "$response" =~ ^[Yy]$ ]]; then exit 0
else exit 1
fi
chmod +x ~/.local/bin/ai-review
chmod +x ~/.local/bin/ai-review
chmod +x ~/.local/bin/ai-review
# In your project directory
cat > .-weight: 500;">git/hooks/pre-commit << 'EOF'
#!/bin/bash
~/.local/bin/ai-review
EOF chmod +x .-weight: 500;">git/hooks/pre-commit
# In your project directory
cat > .-weight: 500;">git/hooks/pre-commit << 'EOF'
#!/bin/bash
~/.local/bin/ai-review
EOF chmod +x .-weight: 500;">git/hooks/pre-commit
# In your project directory
cat > .-weight: 500;">git/hooks/pre-commit << 'EOF'
#!/bin/bash
~/.local/bin/ai-review
EOF chmod +x .-weight: 500;">git/hooks/pre-commit
mkdir -p ~/.-weight: 500;">git-templates/hooks
cp ~/.local/bin/ai-review ~/.-weight: 500;">git-templates/hooks/pre-commit
-weight: 500;">git config --global init.templateDir ~/.-weight: 500;">git-templates
mkdir -p ~/.-weight: 500;">git-templates/hooks
cp ~/.local/bin/ai-review ~/.-weight: 500;">git-templates/hooks/pre-commit
-weight: 500;">git config --global init.templateDir ~/.-weight: 500;">git-templates
mkdir -p ~/.-weight: 500;">git-templates/hooks
cp ~/.local/bin/ai-review ~/.-weight: 500;">git-templates/hooks/pre-commit
-weight: 500;">git config --global init.templateDir ~/.-weight: 500;">git-templates
-weight: 500;">git add suspicious-code.py
-weight: 500;">git commit -m "add feature"
-weight: 500;">git add suspicious-code.py
-weight: 500;">git commit -m "add feature"
-weight: 500;">git add suspicious-code.py
-weight: 500;">git commit -m "add feature"
π Running local code review... Issues found: 1. **SQL Injection** (line 23): User input passed directly to query. Use parameterized queries instead. 2. **Missing null check** (line 45): `user.profile` accessed without verifying user exists. 3. **Hardcoded credential** (line 12): API key in source code. Move to environment variable. ---
Review complete. Commit? [y/N]
π Running local code review... Issues found: 1. **SQL Injection** (line 23): User input passed directly to query. Use parameterized queries instead. 2. **Missing null check** (line 45): `user.profile` accessed without verifying user exists. 3. **Hardcoded credential** (line 12): API key in source code. Move to environment variable. ---
Review complete. Commit? [y/N]
π Running local code review... Issues found: 1. **SQL Injection** (line 23): User input passed directly to query. Use parameterized queries instead. 2. **Missing null check** (line 45): `user.profile` accessed without verifying user exists. 3. **Hardcoded credential** (line 12): API key in source code. Move to environment variable. ---
Review complete. Commit? [y/N]
# Add to the script, after getting DIFF
LINES_CHANGED=$(echo "$DIFF" | grep -c "^+" || true)
if [ "$LINES_CHANGED" -lt 5 ]; then echo "Small change, skipping review" exit 0
fi
# Add to the script, after getting DIFF
LINES_CHANGED=$(echo "$DIFF" | grep -c "^+" || true)
if [ "$LINES_CHANGED" -lt 5 ]; then echo "Small change, skipping review" exit 0
fi
# Add to the script, after getting DIFF
LINES_CHANGED=$(echo "$DIFF" | grep -c "^+" || true)
if [ "$LINES_CHANGED" -lt 5 ]; then echo "Small change, skipping review" exit 0
fi
# Only review Python and JavaScript
DIFF=$(-weight: 500;">git diff --cached --diff-filter=ACMR -- '*.py' '*.js' '*.ts')
# Only review Python and JavaScript
DIFF=$(-weight: 500;">git diff --cached --diff-filter=ACMR -- '*.py' '*.js' '*.ts')
# Only review Python and JavaScript
DIFF=$(-weight: 500;">git diff --cached --diff-filter=ACMR -- '*.py' '*.js' '*.ts')
# Skip the hook for quick fixes
-weight: 500;">git commit --no-verify -m "typo fix"
# Skip the hook for quick fixes
-weight: 500;">git commit --no-verify -m "typo fix"
# Skip the hook for quick fixes
-weight: 500;">git commit --no-verify -m "typo fix"
# Append to script before the prompt
REVIEW_LOG=~/.local/share/ai-reviews/$(date +%Y-%m-%d).log
mkdir -p "$(dirname "$REVIEW_LOG")"
echo "=== $(date) ===" >> "$REVIEW_LOG"
echo "$DIFF" >> "$REVIEW_LOG"
# Append to script before the prompt
REVIEW_LOG=~/.local/share/ai-reviews/$(date +%Y-%m-%d).log
mkdir -p "$(dirname "$REVIEW_LOG")"
echo "=== $(date) ===" >> "$REVIEW_LOG"
echo "$DIFF" >> "$REVIEW_LOG"
# Append to script before the prompt
REVIEW_LOG=~/.local/share/ai-reviews/$(date +%Y-%m-%d).log
mkdir -p "$(dirname "$REVIEW_LOG")"
echo "=== $(date) ===" >> "$REVIEW_LOG"
echo "$DIFF" >> "$REVIEW_LOG" - You're working with sensitive code
- You hit rate limits at 2 AM debugging
- Your company's security policy says no external AI
- You don't want to pay per token for every commit - Ollama - Dead simple local LLM runner
- A decent GPU - 8GB VRAM minimum, 16GB recommended
- Git - Obviously
- 10 minutes - That's genuinely it - deepseek-coder:6.7b - ~3 seconds for typical diffs
- codellama:13b - ~8 seconds, slightly better catches
- codellama:34b - ~25 seconds, overkill for pre-commit - Complex architectural issues
- Business logic errors (it doesn't know your domain)
- Subtle race conditions
- Whether your code actually solves the right problem - Fewer "oops" commits - It catches the dumb mistakes I make at midnight
- Faster PR reviews - Human reviewers focus on architecture, not typos
- Better habits - Knowing there's a check makes me write cleaner first drafts