Code reviews are not the hard part of shipping. They are the slow part.
Not because the feedback is difficult to give — because most of it is mechanical. Pointing out that a variable name is ambiguous, that an error is not being handled, that a helper function already exists three files over. That is not high-order thinking. That is cognitive tax on work that should have been caught before the PR was opened.
The result: reviewers burn their best focus on style nits and boilerplate comments. Real issues — the ones about architecture, data flow, edge cases that will haunt you in production — get a skim. PRs pile up. Context decays. The person who opened the PR rewrites the diff in their head three times before anyone looks at it.
These 10 Claude prompts fix the mechanical half. They are the ones I actually use — not assembled from a listicle, but refined through the kind of trial and error that happens when a PR queue gets genuinely out of hand. Each prompt is copy-paste ready, explained so you know when to reach for it, and paired with a real example showing what useful output looks like.
Here is what to do with them.
Before You Start: How to Feed Claude a Diff
No plugin, no IDE extension, no setup required. The fastest path: run git diff HEAD~1 | pbcopy (macOS) or git diff HEAD~1 | xclip (Linux), then paste directly into Claude's chat window. For a full file review, just open the file and paste the contents. For a PR diff from GitHub, hit the "Files changed" tab, copy the raw diff, paste it in. That's the entire workflow. The 10 prompts below are designed to be dropped in immediately after your diff — each one targets a different failure mode that human reviewers consistently miss.
The 10 Prompts
1. Find Bugs, Not Style Issues
The Prompt
Review this diff for logic errors, null dereferences, race conditions, and off-by-one errors only. Ignore formatting, naming conventions, and style. For each bug found, show me the exact line, explain why it's a bug, and suggest a fix. If you find nothing, say so explicitly.
[paste diff here]
What it does
Focuses Claude entirely on correctness bugs — the ones that ship to production. By explicitly excluding style feedback, you stop Claude from burying a real null deref under six comments about variable naming.
Example output
Bug 1 — Null dereference (line 34)
const user = await db.findUser(id)
return user.profile.email // user can be null if id not found
Fix:
const user = await db.findUser(id)
if (!user) throw new NotFoundError(`User ${id} not found`)
return user.profile.email
Bug 2 — Race condition (lines 51-58)
async function incrementCounter(key: string) {
const val = await redis.get(key) // read
await redis.set(key, Number(val) + 1) // write — not atomic
}
Fix: Use redis.incr(key) instead — single atomic operation.
No other bugs found.
Why it works
Negative constraint prompting ("ignore X") prevents attention dilution — Claude stays on the high-signal task.
Pro tip — Pipe in a specific file instead of a diff: cat src/payments.ts | pbcopy, then add "focus on the payment processing logic only."
2. Review This Diff as a Senior Engineer on the Team
The Prompt
You are a senior engineer who has worked on this codebase for 2 years. You care about maintainability, pragmatic architecture, and not over-engineering. Review this diff. Comment on architectural trade-offs, whether abstractions are at the right level, and anything that will hurt the team in 6 months. Be direct, not diplomatic.
[paste diff here]
What it does
Shifts Claude out of generic reviewer mode into an opinionated senior engineer persona. The "2 years on this codebase" framing produces taste-driven feedback about abstractions and trade-offs, not just correctness.
Example output
The new UserService class looks fine on the surface, but you've split
what was one simple fetch + transform into 4 layers: repository,
service, mapper, and DTO. For a read-only endpoint that never changes
business rules, that's accidental complexity.
The mapper is doing work that could be a one-liner in the controller:
// Before
return UserMapper.toResponseDTO(UserMapper.fromEntity(entity))
// What you actually need
return { id: entity.id, name: entity.name, email: entity.email }
The abstraction will pay off if the domain logic grows. Right now it
doesn't exist. I'd collapse the mapper until you actually need it.
The repository pattern here is fine — keep it. DB access should be
abstracted. The service layer boundary is also correct. Just cut the mapper.
Why it works
Role + tenure + value system = highly specific persona. Claude produces opinions, not hedged suggestions.
Pro tip — Add your actual stack: "You're a senior Go engineer who hates global state and prefers explicit error returns over panics."
3. What Edge Cases Does This Code Miss?
The Prompt
Given this code, list every edge case it does not handle. Think about: empty inputs, nil/null values, boundary values (0, -1, MaxInt), concurrent access, network timeouts, malformed data, and unexpected types. For each edge case, show what would currently happen and what should happen instead.
[paste diff or function here]
What it does
Forces a systematic adversarial pass through your code. This is the prompt to run before writing tests — it maps the territory first, then you can cover it.
Example output
Edge case 1 — Empty slice input
func Average(nums []float64) float64 {
total := 0.0
for _, n := range nums { total += n }
return total / float64(len(nums)) // divide by zero if len == 0
}
Currently: panic: runtime error: floating-point division by zero
Should be: return 0, ErrEmptySlice or return math.NaN()
Edge case 2 — Concurrent writes to shared cache
var cache = map[string]string{}
func Set(k, v string) { cache[k] = v }
Currently: data race — map writes are not goroutine-safe
Should be: sync.RWMutex or sync.Map
Edge case 3 — Negative pagination offset
GET /items?page=-1
Currently: generates SQL OFFSET -20, driver-specific behavior
Should be: validate page >= 1 before query execution
Why it works
Enumerated category lists ("think about: X, Y, Z") act as a mental checklist that prevents Claude from stopping at the first few obvious cases.
Pro tip — Append "then write a test for the three most dangerous ones" to get code immediately.
4. Summarize This PR in 3 Bullet Points for a Reviewer With Zero Context
The Prompt
Summarize this PR in exactly 3 bullet points for a reviewer who has never seen this codebase. Each bullet should answer one of: (1) what changed, (2) why it changed, (3) what could go wrong. Be concrete — reference actual class names, function names, and file paths where relevant. No marketing language.
[paste diff here]
What it does
Generates the PR description your team actually needed. Also useful as a sanity check — if Claude can't summarize the PR clearly, the PR is probably too large or lacks coherent scope.
Example output
- **What changed:** `AuthMiddleware` in `src/middleware/auth.ts` now
validates JWT expiry using server time instead of the token's `iat`
claim, fixing a 6-hour clock skew bug that let expired tokens pass.
- **Why it changed:** Tokens issued on mobile clients with misconfigured
clocks were passing validation indefinitely. Reported in issue #441.
- **What could go wrong:** Any client that relies on the old behavior
(token valid for `exp - iat` seconds regardless of server time) will
now get 401s on previously valid tokens. Check `AuthService.refresh()`
— it may need a corresponding update to issue tokens with corrected timestamps.
Why it works
The three-question structure forces completeness — "what/why/risk" covers the full reviewer mental model in minimal words.
Pro tip — Use it in reverse: paste this output back to Claude and ask "does the diff actually match this summary?" to catch scope creep.
5. Does This Change Break Backward Compatibility?
The Prompt
Analyze this diff for backward compatibility issues. Check for: removed or renamed public API methods, changed function signatures, modified database schema without migration, changed JSON field names in request/response bodies, changed environment variable names, and altered default behavior. List each breaking change with its severity (breaking vs. deprecation-worthy) and a migration path.
[paste diff here]
What it does
Runs a compatibility audit against the most common sources of silent breakage — API contracts, schema changes, and configuration drift. Critical before any library release or service deployment.
Example output
Breaking change (BREAKING) — Renamed method
// Before
client.createUser(params: UserParams): Promise<User>
// After
client.addUser(params: CreateUserParams): Promise<UserRecord>
Three things changed: method name, param type name, return type name.
Any caller using the old signature breaks at compile time (TypeScript)
or runtime (JavaScript). Migration: keep createUser as a deprecated
alias for one minor version.
Breaking change (BREAKING) — Schema change without migration
// users table: `display_name` column renamed to `full_name`
// No migration file found in /db/migrations/
Existing rows are unaffected but all queries using `display_name`
will fail with column-not-found. Need: migration + ORM model update
+ API response field rename (separate BC issue).
Deprecation-worthy — Removed default timeout
fetchData() previously defaulted to 5000ms. Now requires explicit
timeout param. Old callers will hang on slow responses.
Why it works
Explicit enumerated checklist ("check for: X, Y, Z") ensures coverage across the 6 most common BC failure modes, not just the obvious ones.
Pro tip — Add your semver policy: "We follow semver. Flag anything requiring a major bump separately."
6. Write the Test Cases This PR Is Missing
The Prompt
Based on this diff, write the unit tests that are missing. Use [TypeScript/Jest | Python/pytest | Go testing] — match whatever is in the diff. Write actual test code, not descriptions. Cover: the happy path if not already tested, the top 3 edge cases, and at least one failure/error scenario. Use realistic test data, not placeholder strings like "test" or "foo".
[paste diff here]
What it does
Produces runnable test code, not a list of "you should test X." The realistic-data constraint matters — placeholder strings hide bugs that only appear with real-world input shapes.
Example output
# Python/pytest — for process_payment(amount, currency, user_id)
def test_process_payment_success():
result = process_payment(amount=99.99, currency="USD", user_id="usr_abc123")
assert result.status == "succeeded"
assert result.amount_charged == 9999 # stored as cents
def test_process_payment_zero_amount_raises():
with pytest.raises(ValueError, match="Amount must be greater than 0"):
process_payment(amount=0, currency="USD", user_id="usr_abc123")
def test_process_payment_unsupported_currency():
with pytest.raises(UnsupportedCurrencyError):
process_payment(amount=10.00, currency="XYZ", user_id="usr_abc123")
def test_process_payment_invalid_user_id_raises():
with pytest.raises(UserNotFoundError):
process_payment(amount=10.00, currency="USD", user_id="usr_NOTREAL")
def test_process_payment_negative_amount_raises():
with pytest.raises(ValueError):
process_payment(amount=-5.00, currency="USD", user_id="usr_abc123")
Why it works
"Write actual code, not descriptions" is the critical constraint. Without it, Claude defaults to a bulleted test plan. The language-matching instruction keeps output paste-ready.
Pro tip — Add "also write a test that would have caught the bug this PR fixes" if there's a bug fix in the diff.
7. Rate the Error Handling: What Fails Silently?
The Prompt
Audit the error handling in this code. Find: swallowed exceptions (catch blocks that don't re-throw or log), errors returned but never checked by callers, operations that can fail but have no error path, and places where errors are logged but execution continues incorrectly. Rate overall error handling 1-10 and explain the rating.
[paste diff here]
What it does
Targets the category of bugs that cause 2am incidents — the ones that don't throw, don't log, just silently corrupt state or return wrong data. The numeric rating gives you a quick signal for whether to approve or request changes.
Example output
Rating: 4/10
Issues found:
1. Swallowed exception (line 23)
try {
await sendWelcomeEmail(user.email)
} catch (e) {
// TODO: handle this
}
User creation succeeds, email silently drops. Caller has no idea.
Fix: log the error + consider whether email failure should fail
the whole operation or be queued for retry.
2. Unchecked return value (line 41)
db.exec("DELETE FROM sessions WHERE user_id = ?", userId)
// result is ignored — could be sql.ErrNoRows, driver error, etc.
Fix: check err return; a failed session delete is a security issue.
3. Error logged, execution continues incorrectly (lines 67-72)
if err := cache.Set(key, val); err != nil {
log.Error("cache write failed", err)
}
// then immediately reads from cache as if write succeeded
Fix: branch on err; fall back to DB read or abort the operation.
Why it works
Naming the specific anti-patterns ("swallowed exceptions," "never checked") gives Claude a concrete taxonomy to scan against rather than a vague "check error handling."
Pro tip — Add "flag anything that would cause data inconsistency if it fails silently" to prioritize the critical issues.
8. Explain This Code to a New Team Member
The Prompt
Explain this code to a new team member joining the project today. They're a competent developer but have zero context on this codebase. Cover: what this code does, why it exists (what problem it solves), any non-obvious decisions or gotchas, and what they'd need to know to safely modify it. Write it as a comment block they could paste above the function.
[paste function or file here]
What it does
Acts as a readability gut-check: if Claude can't explain the code clearly, the code probably can't explain itself. The comment-block output format means you get a usable artifact, not just a review opinion.
Example output
// OrderProcessor handles payment capture and inventory reservation
// in a single atomic-ish operation.
//
// WHY THIS EXISTS: We had incidents where payments succeeded but
// inventory reservation failed, leaving orders in a zombie state.
// This processor uses a two-phase approach: reserve inventory first
// (cheap to roll back), then capture payment. If payment fails,
// inventory is released.
//
// NON-OBVIOUS DECISIONS:
// - ReserveInventory uses a 30s advisory lock, not a transaction.
// The lock TTL is intentional: long enough for payment (typically
// <5s) but short enough to self-heal if the process dies.
// - We call Stripe's capture endpoint, not charge. Pre-auth happens
// at checkout. This function only finalizes it.
//
// SAFE TO MODIFY IF:
// - You understand the inventory lock lifecycle (see InventoryService)
// - You've read the Stripe capture docs — partial captures behave
// differently from full captures
// - You test with the "payment_intent.payment_failed" webhook fixture
//
// DO NOT: add sleeps or synchronous external calls between Reserve and
// Capture. The lock window is tight by design.
func (p *OrderProcessor) Process(ctx context.Context, order Order) error {
Why it works
Asking for a paste-ready comment block makes the output immediately actionable and forces Claude to be structured rather than conversational.
Pro tip — Ask "what would a new engineer most likely break if they modified this?" for a focused risk summary.
9. Suggest a Simpler Way to Achieve the Same Result
The Prompt
Look at this code and suggest a simpler implementation that achieves the same result. Prioritize: fewer lines, less state, built-in language/library features over custom code. Show the simplified version side by side with the original. Only suggest changes that are genuinely simpler — don't trade one kind of complexity for another. If the code is already optimal, say so.
[paste function or block here]
What it does
Runs a complexity-reduction pass. Catches over-engineered solutions, unnecessary abstractions, and hand-rolled versions of standard library functions. The "don't trade one complexity for another" constraint prevents Claude from suggesting clever one-liners that are harder to read.
Example output
// Original (31 lines)
function groupByStatus(orders: Order[]): Record<string, Order[]> {
const result: Record<string, Order[]> = {}
for (const order of orders) {
if (!result[order.status]) {
result[order.status] = []
}
result[order.status].push(order)
}
return result
}
// Simplified (1 line, same behavior)
const groupByStatus = (orders: Order[]) =>
Object.groupBy(orders, o => o.status)
// Note: Object.groupBy is available in Node 22+ / TS 5.4+
// If targeting older environments, use:
const groupByStatus = (orders: Order[]) =>
orders.reduce((acc, o) => ({
...acc,
[o.status]: [...(acc[o.status] ?? []), o]
}), {} as Record<string, Order[]>)
Why it works
The "only suggest genuinely simpler" constraint prevents the common failure mode of Claude suggesting complex abstractions in the name of simplification.
Pro tip — Add "flag any simplification that changes behavior in edge cases" to stay safe during the refactor.
10. Write the Review Comment I Should Leave on This Code
The Prompt
I need to leave a code review comment on this diff. The issue I want to flag is: [describe the issue in plain English]. Write the review comment I should post. It should be: specific (reference the exact lines), constructive (explain why, not just what), actionable (include a suggestion or example fix), and professional but direct. Avoid filler phrases like "Great work, but..." Write it as plain text I can paste directly into GitHub.
[paste the specific lines in question]
[describe the issue: e.g. "this function is doing too much, the DB query and business logic are mixed together"]
What it does
Eliminates the blank-page problem when you know something is wrong but can't phrase it constructively. Also useful for junior reviewers learning to write good review comments — the output is a template they can study.
Example output
Lines 45-67: This function is handling both the database query and the
discount calculation logic, which makes it hard to test either piece in isolation.
The SQL on line 48 reaches directly into order_items to compute the
total, then the loop on lines 52-61 applies business rules on top.
If the discount logic changes (which it will — marketing owns this),
you'll need to touch the same function that owns the raw DB query.
Suggestion: extract the discount logic into a pure function:
// Pure, testable, no DB dependency
function applyDiscounts(subtotal: number, coupons: Coupon[]): number {
...
}
Then call it from the service after the DB fetch. The repository layer
stays ignorant of business rules, and you can unit test applyDiscounts
without mocking a database.
Why it works
Filling in the issue in plain English lets you offload the translation from "I know something's wrong" to "I can articulate exactly why" — Claude does the translation into review-ready language.
Pro tip — Add "make the tone [direct | gentle | questioning]" to match the culture of your team or the seniority of the author.
The Bigger Principle
These prompts work for one reason: they constrain Claude's output to what a reviewer actually needs. Not a summary of what the code does. Not a list of every possible improvement. A targeted analysis of the specific thing most likely to cause a problem — delivered in a format you can act on in the next five minutes.
That is the principle worth keeping. Generic prompts produce generic feedback. The more precisely you describe the review task — the language, the risk surface, the context of where this code runs — the more useful the output.
Start with one prompt. The one that covers the review task you dread most: the security check you always feel slightly uncertain about, the refactor suggestion you struggle to articulate diplomatically, the consistency audit you keep skipping because it takes too long. Build from there. A personal library of 10 well-tuned prompts beats a library of 100 you never open.
Bookmark this. Next time a PR sits in your queue, paste one of these before you write a single comment.
Want the full pack?
This article covers 10 prompts. The Claude Prompts for Developers pack includes 55 prompts across 6 categories — debugging, architecture, docs, productivity, and 5 multi-step power combos. One-time download, copy-paste ready.
And if you want to go further — there is a Claude Prompts for Developers pack with 55 battle-tested prompts covering code review, debugging, architecture analysis, documentation, and productivity. The same kind of prompts. Built for daily use.
Top comments (1)
I built this list from my own PR queue — these are the prompts I actually reach for, not theoretical examples.
The two that save me the most time:
What's the code review task you dread most? Drop it below and I'll share the exact prompt I use for it.