Code reviews are essential — but they're slow, inconsistent, and depend heavily on who's reviewing that day.
Claude Code's /code-review skill changes this. It evaluates your code across 5 dimensions every time, with zero reviewer fatigue. Here's how to put it to work on real code.
What /code-review Actually Does
Run /code-review inside Claude Code and it analyzes the target code across five axes:
| Dimension | What It Checks |
|---|---|
| Design | Separation of concerns, SOLID principles, dependency structure |
| Readability | Naming conventions, comments, cyclomatic complexity |
| Security | Input validation, auth flows, OWASP Top 10 |
| Testability | Mockability, side effect isolation, dependency injection |
| Performance | N+1 queries, unnecessary computation, memory leaks |
Basic usage:
# Review current directory
/code-review
# Review a specific file
/code-review src/auth/login.py
# Review only the diff since last commit
/code-review --diff HEAD~1
Example 1: Catching an Auth Vulnerability in Python
The Code (with bugs)
import sqlite3
def login(username, password):
conn = sqlite3.connect("app.db")
cursor = conn.cursor()
# Direct string interpolation — SQL injection waiting to happen
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
cursor.execute(query)
user = cursor.fetchone()
conn.close()
return user is not None
/code-review Output
[Security] CRITICAL
- SQL injection: f-string used to build query. Switch to parameterized queries.
- Plaintext password comparison: use bcrypt.checkpw() or equivalent.
- Fix: cursor.execute("SELECT...", (username, password_hash))
[Design] MEDIUM
- DB connection mixed with business logic. Extract to a Repository layer.
- conn.close() must be guaranteed via try/finally or a context manager.
[Testability] MEDIUM
- Direct DB dependency makes unit testing impossible.
- Inject the connection (or a factory) as a parameter.
[Performance] LOW
- Reconnecting on every call is expensive. Consider a connection pool.
[Readability] LOW
- Missing return type hint. Add -> bool.
Fixed Version
import sqlite3
import bcrypt
from contextlib import contextmanager
@contextmanager
def get_db(path: str = "app.db"):
conn = sqlite3.connect(path)
try:
yield conn
finally:
conn.close()
def login(username: str, password: str, db_path: str = "app.db") -> bool:
with get_db(db_path) as conn:
cursor = conn.cursor()
cursor.execute(
"SELECT password_hash FROM users WHERE username = ?",
(username,)
)
row = cursor.fetchone()
if row is None:
return False
return bcrypt.checkpw(password.encode(), row[0].encode())
Two critical vulnerabilities caught before the PR was opened.
Example 2: JavaScript async/await Error Handling
The Code (with bugs)
async function fetchUserData(userId) {
const response = await fetch(`/api/users/${userId}`);
const data = response.json(); // missing await
return data.name;
}
/code-review Output
[Design] CRITICAL
- response.json() is missing await. Returns a Promise, so data.name is always undefined.
[Security] MEDIUM
- userId interpolated directly into URL. Use encodeURIComponent() on the client side.
- Server-side validation is also required.
[Error Handling] CRITICAL
- No try/catch for network failures.
- No response.ok check — 4xx/5xx responses pass through silently.
[Testability] MEDIUM
- fetch is referenced globally. Accept fetchFn as a parameter for easy mocking in tests.
Fixed Version
async function fetchUserData(userId, fetchFn = fetch) {
const safeId = encodeURIComponent(userId);
let response;
try {
response = await fetchFn(`/api/users/${safeId}`);
} catch (err) {
throw new Error(`Network error: ${err.message}`);
}
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
const data = await response.json();
return data.name;
}
The missing await would have caused a silent runtime bug. The error handling gaps would have made debugging a nightmare in production.
Pairing /code-review with /refactor-suggest
The two commands work well together in a loop:
Step 1: /code-review src/auth/login.py
→ Get the problem list
Step 2: /refactor-suggest src/auth/login.py --focus security
→ Generate a targeted fix for the security issues only
Step 3: Review and apply the generated code
Step 4: /code-review src/auth/login.py
→ Confirm improvement, repeat for next axis
Using --focus keeps each iteration scoped and reviewable. Don't try to fix all five dimensions in one pass.
Automating PR Reviews with GitHub Actions
Combine /code-review with GitHub Actions to post review comments automatically on every pull request:
# .github/workflows/code-review.yml
name: AI Code Review
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run /code-review on PR diff
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
npx claude-code /code-review --diff origin/main \
--output-format github-comment \
--post-to-pr ${{ github.event.pull_request.number }}
With this setup:
- First-pass review happens instantly, with no reviewer availability dependency
- The 5-axis evaluation is consistent across every PR, every time
- Human reviewers can focus on design decisions and business logic
The Real Value
The examples above caught a SQL injection vulnerability and a broken async function — both of which would have shipped to production without this review. The review took seconds.
The consistency is the key differentiator. Human reviewers are good, but their attention varies. /code-review applies the same scrutiny to the 47th PR of the week as it does to the first.
Code Review Pack
If you want a ready-made setup — custom 5-axis review templates, GitHub Actions workflow files, and /refactor-suggest integration patterns — I've packaged everything in the Code Review Pack (¥980).
Get the Code Review Pack → PromptWorks
It's the same workflow I use to maintain review quality without blocking shipping velocity.
What's the worst bug a code review caught for you before it shipped? I'm always curious what slips through without a second pair of eyes.
Top comments (0)