Module 4: Code Review of AI Output
What to Look For First: The Priority Pyramid
What to Look For First: The Priority Pyramid
Capsule overview
You can't review everything. Nor should you. A professional code review isn't an exhaustive line-by-line reading — it's a strategic search for what matters. If you only have 5 minutes to review a PR generated by Claude Code, what do you review? If you have 30 minutes, how do you distribute the time? The answer is in the priority pyramid: a clear hierarchy that tells you what to review first, what to review next, and what you can leave for the end.
The pyramid isn't a suggestion — it's a protocol. Following it protects you from the most common trap in code review: spending 20 minutes discussing whether a variable name is descriptive while a SQL injection goes unnoticed. Priority, not exhaustiveness.
The Priority Pyramid
▲
/ \
/ 1 \ ← SECURITY
/ \ Always. No exception.
/-------\
/ 2 \ ← BUSINESS LOGIC
/ \ Does it do what the business needs?
/-------------\
/ 3 \ ← EDGE CASES
/ \ What happens with unexpected inputs?
/-------------------\
/ 4 \ ← PERFORMANCE
/ \ Is it efficient? (Only when relevant)
/-------------------------\
/ 5 \ ← STYLE AND CONVENTIONS
/ \ Naming, formatting, consistency
/-------------------------------\
The golden rule
Never review a lower level without having covered the ones above it. If you found a SQL injection (level 1) and you haven't resolved it, there's no point in reviewing whether the variable names are descriptive (level 5). This seems obvious written down, but in practice it's surprisingly easy to fall into the trap: style is the most visible and the easiest to have opinions about.
Level 1: Security — Always, No Exception
Why it's the highest level
A security bug can mean leaked data, compromised accounts, or regulatory fines. A logic bug loses money or time. A style bug loses nothing. The difference in impact is orders of magnitude.
What to look for specifically
SECURITY — Quick checklist:
□ Are there hardcoded secrets? (API keys, passwords, tokens, connection strings)
□ Do the SQL queries use parameterized queries?
□ Do the sensitive endpoints have authentication and authorization?
□ Do the tokens have expiration?
□ Are the user inputs sanitized?
□ Is sensitive data encrypted in transit and at rest?
□ Is there rate limiting on public endpoints?
□ Do the error messages NOT expose internal information?
Example: What AI generates vs what it should be
Claude Code generates a search endpoint:
from fastapi import FastAPI, Query
import sqlite3
app = FastAPI()
@app.get("/users/search")
async def search_users(name: str = Query(...)):
conn = sqlite3.connect("app.db")
cursor = conn.cursor()
cursor.execute(f"SELECT * FROM users WHERE name LIKE '%{name}%'")
results = cursor.fetchall()
conn.close()
return {"users": results}
What a security review detects in 30 seconds:
❌ SQL Injection: an f-string with user input directly in the SQL
Attack: name = "'; DROP TABLE users; --"
❌ No authentication: anyone can search users
❌ Exposes all fields: SELECT * includes password_hash, email, etc.
⚠️ No rate limiting: an attacker can enumerate the whole table
The corrected version:
from fastapi import FastAPI, Query, Depends
from fastapi.security import OAuth2PasswordBearer
from pydantic import BaseModel
from typing import List
import sqlite3
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class UserSearchResult(BaseModel):
id: int
name: str
created_at: str
@app.get("/users/search", response_model=List[UserSearchResult])
async def search_users(
name: str = Query(..., min_length=1, max_length=100),
token: str = Depends(oauth2_scheme),
):
conn = sqlite3.connect("app.db")
cursor = conn.cursor()
cursor.execute(
"SELECT id, name, created_at FROM users WHERE name LIKE ?",
(f"%{name}%",),
)
results = cursor.fetchall()
conn.close()
return [
UserSearchResult(id=r[0], name=r[1], created_at=r[2])
for r in results
]
Recommended time
- Simple code (CRUD, utils): 2-5 minutes
- Code with auth/sensitive data: 10-15 minutes
- Financial/compliance code: 20-30 minutes + review by a second pair of eyes
Level 2: Business Logic — Does It Do What the Business Needs?
Why it's the second level
Incorrect business logic is the most expensive error after security. A miscalculated discount, an inverted filter, an incorrect condition — these errors pass tests, pass linters, look professional, but do something different from what the business needs. And with AI code, this risk multiplies because AI doesn't understand your business.
What to look for specifically
BUSINESS LOGIC — Quick checklist:
□ Are the calculations correct? (prices, taxes, discounts, totals)
□ Are the conditions correct? (>, <, >=, <=, ==, !=)
□ Do the filters include/exclude the right thing?
□ Are the states and transitions valid?
□ Are the business rules complete? (not just the happy path)
□ Does the code solve the problem you asked for, not a slightly different one?
Example: The discount that looks correct but isn't
Requirement: "A 15% discount for purchases over $100."
Claude Code generates:
from pydantic import BaseModel
from decimal import Decimal
class Order(BaseModel):
items: list
subtotal: Decimal
def calculate_discount(order: Order) -> Decimal:
if order.subtotal >= Decimal("100"):
discount = order.subtotal * Decimal("0.15")
return order.subtotal - discount
return order.subtotal
What a business logic review detects:
⚠️ Condition: >= 100 vs > 100
"Over $100" → should be > 100, not >= 100
Impact: an order of exactly $100 gets a discount when it shouldn't
⚠️ Return: returns the total with discount, not the discount amount
The name "calculate_discount" suggests it returns the discount
But it returns the subtotal minus the discount
What did the calling code expect from this function?
⚠️ There's no cap on the discount
Is there a maximum discount amount? $500? $1000?
The requirement doesn't say, but the business probably has one
Notice how none of these issues is a "bug" in the traditional sense. The code compiles, runs correctly, and a basic test passes. But it doesn't do what the business needs.
Recommended time
- Simple logic (CRUD with no rules): 1-3 minutes
- Business rules (discounts, permissions): 5-15 minutes
- Financial or compliance logic: 15-30 minutes
Level 3: Edge Cases — What Happens When Things Go Wrong?
Why it's the third level
Edge cases are where code goes from "works in a demo" to "works in production." AI is notoriously bad at edge cases because it trains on "happy path" examples. The code it generates works with ideal inputs and breaks with real inputs.
What to look for specifically
EDGE CASES — Quick checklist:
□ What happens with None/null?
□ What happens with empty lists?
□ What happens with empty strings?
□ What happens with negative numbers or zero?
□ What happens with the first and last element?
□ What happens with very large values?
□ What happens if the database is empty?
□ What happens if the external service doesn't respond?
□ What happens with concurrent requests?
Example: The pagination that crashes
Claude Code generates pagination:
from fastapi import FastAPI, Query
from typing import List
from pydantic import BaseModel
app = FastAPI()
class PaginatedResponse(BaseModel):
items: list
total: int
page: int
pages: int
@app.get("/items", response_model=PaginatedResponse)
async def list_items(
page: int = Query(default=1, ge=1),
per_page: int = Query(default=20, ge=1, le=100),
):
all_items = get_all_items_from_db()
total = len(all_items)
pages = total // per_page
start = (page - 1) * per_page
end = start + per_page
items = all_items[start:end]
return PaginatedResponse(
items=items,
total=total,
page=page,
pages=pages,
)
What an edge case review detects:
❌ pages = total // per_page
If there are 21 items and per_page=20 → pages = 1 (should be 2)
Fix: pages = (total + per_page - 1) // per_page or math.ceil(total / per_page)
❌ page > pages
If you ask for page=5 but there are only 2 pages → returns empty items with no error
Should it return 404? Or an empty list with correct metadata?
❌ Empty database
total = 0, pages = 0, page = 1 → page > pages but there's no error
⚠️ all_items = get_all_items_from_db()
Loads ALL the items into memory before paginating
With 1M items, this crashes from memory
It should paginate in the SQL query, not in Python
Recommended time
- Code with no user inputs: 1-2 minutes
- Code with validated inputs: 3-5 minutes
- Code with free/complex inputs: 5-10 minutes
Level 4: Performance — Is It Efficient? (When Relevant)
Why it's the fourth level
Performance matters, but it matters less than security, business logic, and edge cases. A slow endpoint is an inconvenience. An insecure endpoint is a disaster. Review performance only when it's relevant — not in every PR.
When it's relevant
Review performance when:
✅ The endpoint handles large volumes of data
✅ The code runs in a loop or batch
✅ There are database queries in loops (N+1)
✅ The code runs on every request (middleware)
✅ There's processing of large files
✅ The system has defined latency requirements
Do NOT review performance when:
❌ It's an admin endpoint used 3 times a day
❌ It's a script that runs once
❌ It's a prototype/MVP
❌ There's no data that performance is a problem
Example: The classic N+1 that AI generates
Claude Code generates an endpoint for users with their orders:
from fastapi import FastAPI
from typing import List
app = FastAPI()
@app.get("/users-with-orders")
async def get_users_with_orders():
users = db.query("SELECT * FROM users")
result = []
for user in users:
orders = db.query(
"SELECT * FROM orders WHERE user_id = ?",
(user["id"],),
)
result.append({
"user": user,
"orders": orders,
"total_orders": len(orders),
})
return result
What a performance review detects:
❌ N+1 Query Problem
If there are 100 users → 1 query (users) + 100 queries (orders) = 101 queries
If there are 10,000 users → 10,001 queries
Fix with JOIN:
SELECT u.*, o.* FROM users u LEFT JOIN orders o ON u.id = o.user_id
Or with 2 queries:
1. SELECT * FROM users
2. SELECT * FROM orders WHERE user_id IN (1, 2, 3, ...)
⚠️ SELECT *
Fetches all the columns even if they're not needed
Fix: specify the needed columns
⚠️ No pagination
If there are 10,000 users with 50 orders each → 500,000 rows in memory
Recommended time
- Low-frequency code: 0 minutes (skip)
- Simple high-frequency code: 2-3 minutes
- Code with complex queries/loops: 5-10 minutes
Level 5: Style and Conventions — The Least Important
Why it's the last level
Style doesn't affect the code's functionality, security, or performance. A bad variable name doesn't cause bugs (except in extreme cases of confusion). Reviewing style is useful for maintainability, but it should never consume more than 10% of your review time.
What AI generally does well
AI is consistently good at:
✅ Formatting (indentation, spaces)
✅ Language style conventions
✅ Docstrings and type hints
✅ File structure
✅ Import ordering
What's worth reviewing
Only review style when:
□ The names are actively confusing (not just "not ideal")
□ The code violates the project's conventions (not the language's)
□ The structure makes it hard to understand the flow
□ There's obvious inconsistency with the rest of the codebase
Example: Where style does matter
def process(d, f=True):
r = []
for i in d:
if f:
x = transform_a(i)
else:
x = transform_b(i)
if x > 0:
r.append(x)
return r
Here it is worth commenting: d, f, r, x, i are unacceptable names. But this is rare in AI code — Claude Code generally uses descriptive names. If you see this, it's probably a sign that something went wrong with the prompt.
Recommended time
- In most cases: 0-1 minute (quick visual review)
- If something looks confusing: 2-3 minutes maximum
The Pyramid in Action: Time Distribution
If you have 5 minutes
5 minutes available:
├── 4 min → Security (level 1)
│ ├── Hardcoded secrets?
│ ├── SQL injection?
│ └── Auth on sensitive endpoints?
└── 1 min → Business logic (level 2)
└── Does the code do what you asked? (quick read)
If you have 15 minutes
15 minutes available:
├── 5 min → Security (level 1)
│ └── Complete security checklist
├── 5 min → Business logic (level 2)
│ └── Verify conditions, calculations, filters
├── 3 min → Edge cases (level 3)
│ └── null, empty, limits
└── 2 min → Quick visual (levels 4-5)
└── Does anything look off?
If you have 30 minutes
30 minutes available:
├── 8 min → Security (level 1)
│ └── Complete checklist + verify against OWASP
├── 10 min → Business logic (level 2)
│ └── Trace every business rule in the code
├── 7 min → Edge cases (level 3)
│ └── All inputs, all error scenarios
├── 3 min → Performance (level 4)
│ └── N+1, queries in loops, memory load
└── 2 min → Style (level 5)
└── Naming, project conventions
If you have 60+ minutes (exhaustive review)
If you have more than 60 minutes, the PR is probably too big. Ask for it to be split into smaller PRs. PRs over 200-300 lines have significantly lower review quality.
Why This Order and Not Another
"Why not put performance before edge cases?"
Because an unhandled edge case can cause a crash in production. A performance problem causes slowness. A crash is worse than slowness.
"Why business logic before edge cases?"
Because if the base logic is incorrect, the edge cases don't matter. There's no point in verifying what happens when quantity=0 if the discount is already miscalculated for quantity=15.
"Why is security always #1?"
Because the other levels affect your application. Security affects your users. A miscalculated discount costs you money. A data breach costs your users their privacy and your company its trust.
"Should I never skip a level?"
You can skip downward (not reviewing style or performance), but never upward (don't skip security to jump straight to edge cases). The pyramid is flexible at the base and rigid at the top.
Connection to the Project
How it connects to the capstone project (Module 8)
In the capstone project, the 15-20 planted problems are distributed across the pyramid:
| Level | Expected problems | Severity |
|---|---|---|
| Security | 3-4 | Critical/High |
| Business logic | 3-4 | High |
| Edge cases | 3-4 | Medium/High |
| Performance | 2-3 | Medium |
| Style | 0-1 | Low |
If you follow the pyramid, you'll find the highest-severity issues first. If you start from the bottom, you could spend all your time on style issues and miss the critical ones.
Troubleshooting
Problem 1: "Security feels obvious — do I really need a checklist?"
Cause: The security issues AI generates aren't always obvious. An f-string in a SQL is obvious. A fallback key in os.getenv("SECRET", "default") isn't.
Solution: The checklist isn't for detecting the obvious — it's for not forgetting the subtle. Use the checklist especially when the code "looks clean." That's where security issues hide.
Problem 2: "I don't know enough about security to do the level 1 review"
Cause: Security is a deep topic and it's normal not to master all of it. Solution: The 8 items in the level 1 quick checklist cover 80% of common issues. You don't need to be a security expert — you need to verify those 8 points. If something doesn't pass, investigate or ask for help. That's better than not reviewing.
Problem 3: "The PR has 500 lines and I only have 15 minutes"
Cause: A PR too big for the available time. Solution: Two options: (1) Ask for the PR to be split. (2) If you can't, apply the 15-minute distribution: security first in the most critical files (auth, database, payments), business logic in the main endpoints, edge cases quickly. Document that you did a partial review and which areas you didn't cover.
Problem 4: "I'm not sure which level an issue corresponds to"
Cause: Some issues touch multiple levels. Solution: Classify by the highest impact. An unvalidated input that can cause SQL injection is level 1 (security), not level 3 (edge case). A calculation that fails with negative values could be level 2 (logic) or level 3 (edge case) — classify it as level 2 if the business handles negative values regularly.
Exercises
Exercise 1: Classify issues by level (Easy)
Classify each issue into the correct level of the pyramid (1-5):
- A function is called
get_userbut returns a list of users - A login endpoint has no rate limiting
- A function calculates VAT at 16% but the country has 21% VAT
- A SELECT query fetches all columns when it only needs 2
- An endpoint accepts
page=-1and crashes - A Stripe API key is in the source code
- The
/admin/delete-allendpoint doesn't require authentication - A nested loop has O(n²) complexity in an endpoint called once a month
See solution
-
Level 5 (Style) — Confusing naming but doesn't cause a functional bug. A developer who reads the code will be confused, but the program works.
-
Level 1 (Security) — Rate limiting on login prevents brute force attacks. Without it, an attacker can try thousands of passwords per second.
-
Level 2 (Business logic) — Incorrect VAT is a business error. The code "works" but calculates wrong. Financial and legal impact.
-
Level 4 (Performance) — SELECT * vs specific SELECT. It works correctly but is inefficient. Only relevant if the table has many columns or high volume.
-
Level 3 (Edge case) — An unexpected input (negative) causes a crash. The happy path works, but an unusual input breaks the system.
-
Level 1 (Security) — A hardcoded secret. Anyone with access to the repo has the API key. Critical.
-
Level 1 (Security) — An admin endpoint with no auth. Anyone can delete all the data. Critical.
-
Level 4 (Performance) — O(n²) sounds bad, but if it's called once a month and
nis small, it doesn't matter. Context determines severity.
Note: Issues 2, 6, and 7 are all level 1. This is intentional — security takes many forms and they're all top priority.
Exercise 2: Time distribution (Medium)
You have 20 minutes to review this PR generated by Claude Code. It's an e-commerce endpoint that processes orders. Plan your time distribution before starting the review.
from fastapi import FastAPI, Depends, HTTPException
from pydantic import BaseModel, Field
from typing import List, Optional
from decimal import Decimal
from datetime import datetime
import uuid
import os
app = FastAPI()
SHIPPING_API_KEY = os.getenv("SHIPPING_KEY", "shp_test_12345")
TAX_RATE = 0.08
class OrderItem(BaseModel):
product_id: str
quantity: int = Field(..., ge=1)
unit_price: Decimal
class CreateOrderRequest(BaseModel):
customer_id: str
items: List[OrderItem]
shipping_address: str
coupon_code: Optional[str] = None
class OrderResponse(BaseModel):
order_id: str
subtotal: Decimal
tax: Decimal
shipping: Decimal
total: Decimal
status: str
created_at: datetime
@app.post("/orders", response_model=OrderResponse)
async def create_order(order: CreateOrderRequest):
subtotal = sum(
item.unit_price * item.quantity for item in order.items
)
discount = Decimal("0")
if order.coupon_code:
discount = get_coupon_discount(order.coupon_code, subtotal)
taxable = subtotal - discount
tax = taxable * Decimal(str(TAX_RATE))
shipping = calculate_shipping(
order.shipping_address, len(order.items)
)
total = taxable + tax + shipping
order_record = {
"order_id": str(uuid.uuid4()),
"customer_id": order.customer_id,
"items": [item.model_dump() for item in order.items],
"subtotal": subtotal,
"discount": discount,
"tax": tax,
"shipping": shipping,
"total": total,
"status": "pending",
"created_at": datetime.utcnow(),
}
save_order(order_record)
return OrderResponse(**order_record)
Describe: (1) your time distribution plan, (2) what you look for in each block, (3) what issues you found.
See solution
Distribution plan (20 minutes):
├── 7 min → Security (level 1)
├── 7 min → Business logic (level 2)
├── 4 min → Edge cases (level 3)
└── 2 min → Performance + style (levels 4-5)
Security (7 min):
⚠️ SHIPPING_API_KEY = os.getenv("SHIPPING_KEY", "shp_test_12345")
Fallback with a test key. If not configured in prod, it uses the test key.
→ It should fail if it's not configured, not use a fallback.
⚠️ There's no authentication on the endpoint
Anyone can create orders. Where's the auth?
⚠️ There's no validation of the customer_id
Does the customer exist? Can they create orders?
✅ No SQL injection (no direct SQL visible)
✅ No secrets in the response
Business logic (7 min):
⚠️ TAX_RATE = 0.08 (float, not Decimal)
It later does Decimal(str(TAX_RATE)) — it works, but
why not define it as Decimal from the start?
⚠️ Tax rate hardcoded to 8%
Does it vary by state/country? In real e-commerce, yes.
⚠️ Discount is subtracted from the subtotal before tax
Is that correct? It depends on the jurisdiction.
In some places, tax is calculated on the subtotal BEFORE the discount.
⚠️ get_coupon_discount and calculate_shipping aren't defined
Do they exist? What do they return if the coupon is invalid?
⚠️ There's no stock validation
Do the products exist? Is there enough stock?
Edge cases (4 min):
⚠️ items can be an empty list
List[OrderItem] allows [] — an order with no items
→ Add min_length=1 or validation
⚠️ unit_price has no minimum
It can be 0 or negative (Decimal allows negatives)
→ Add gt=0 validation
⚠️ Invalid coupon_code
If get_coupon_discount raises an exception, there's no try/except
⚠️ What happens if shipping_address is an empty string?
Performance/Style (2 min):
✅ Uses Decimal for amounts (good)
✅ Uses uuid4 for order_id (good)
✅ Descriptive names
⚠️ In-line calculation in the endpoint — could be moved to a service
Summary of findings:
| # | Issue | Level | Severity |
|---|---|---|---|
| 1 | Shipping fallback key | 1 (Sec) | High |
| 2 | No auth on endpoint | 1 (Sec) | High |
| 3 | No customer validation | 1 (Sec) | Medium |
| 4 | Hardcoded tax rate | 2 (Logic) | Medium |
| 5 | Discount before tax (correct?) | 2 (Logic) | Medium |
| 6 | No stock validation | 2 (Logic) | High |
| 7 | Empty items list | 3 (Edge) | Medium |
| 8 | unit_price with no minimum | 3 (Edge) | Medium |
Exercise 3: Prioritize in AI vs human code (Medium)
For each snippet, identify whether the main issue is one you'd find the same way in human code or if it's specific to AI code. Explain why.
Snippet A:
from fastapi import FastAPI
from sklearn.metrics import roc_auc_multiclass
app = FastAPI()
@app.post("/predict")
async def predict(data: dict):
score = roc_auc_multiclass(data["y_true"], data["y_pred"])
return {"score": score}
Snippet B:
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.delete("/users/{user_id}")
async def delete_user(user_id: int):
user = get_user(user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
delete_from_db(user_id)
delete_user_files(user_id)
send_deletion_email(user["email"])
return {"status": "deleted"}
Snippet C:
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional
import abc
import dataclasses
from enum import Enum, IntFlag
app = FastAPI()
class TaskPriority(IntFlag):
LOW = 1
MEDIUM = 2
HIGH = 4
URGENT = 8
CRITICAL = 16
class TaskStatus(str, Enum):
BACKLOG = "backlog"
TODO = "todo"
IN_PROGRESS = "in_progress"
IN_REVIEW = "in_review"
QA = "qa"
STAGING = "staging"
DONE = "done"
ARCHIVED = "archived"
class BaseTaskProcessor(abc.ABC):
@abc.abstractmethod
def process(self, task): ...
@abc.abstractmethod
def validate(self, task): ...
@dataclasses.dataclass
class TaskMetadata:
source: str
processor: str
version: int
class TaskCreate(BaseModel):
title: str
priority: TaskPriority = TaskPriority.MEDIUM
status: TaskStatus = TaskStatus.TODO
@app.post("/tasks")
async def create_task(task: TaskCreate):
return {"id": 1, "title": task.title}
See solution
Snippet A — A SPECIFIC AI issue:
roc_auc_multiclass doesn't exist in scikit-learn. The real function is roc_auc_score with multi_class as a parameter. This is a hallucination — AI invented a function name that sounds real but doesn't exist.
A human developer wouldn't invent a function name from a library they use. They might get the parameters wrong, but they wouldn't invent the function.
Pyramid priority: Level 1 (Security/Functionality) — the code doesn't work at all. It's an ImportError.
Snippet B — An issue you'd find in HUMAN CODE too:
The problem is the lack of atomicity. If delete_user_files fails, the user has already been deleted from the DB but their files persist. If send_deletion_email fails, the user was deleted but not notified.
This is a classic design error that both humans and AI make. It's not specific to AI.
Pyramid priority: Level 2 (Logic) — the operation isn't atomic, it can leave the system in an inconsistent state.
Snippet C — A SPECIFIC AI issue:
Massive over-engineering. The prompt was probably "create an endpoint to create tasks." The code includes:
IntFlagfor priorities (for a simple CRUD, astr Enumis enough)- 8 task states (for an endpoint that only creates, doesn't process)
abc.ABCwith an abstract class nobody's going to implementdataclasses.dataclassfor metadata that's never used- A mix of Pydantic, dataclasses, and abc in the same file
A human developer wouldn't do this for a simple CRUD. AI tends to "show knowledge" by adding patterns that aren't needed.
Pyramid priority: Level 5 (Style) — the code works, but it's unnecessarily complex. However, it's an AI red flag that suggests you should check whether there's more over-engineering in other files.
Exercise 4: Your own pyramid (Hard)
Take a real PR from your work (or generate code with Claude Code) and apply the pyramid:
- Set how much time you have for the review
- Distribute the time according to the pyramid
- Run the review level by level
- Document: what you found, at which level, how much real time you used
See evaluation guide
Your review should demonstrate:
- ✅ Time distribution documented BEFORE starting
- ✅ Level 1 reviewed before level 2
- ✅ More than 50% of the time on levels 1-2
- ✅ Issues classified by level
- ✅ Real vs planned time documented
It should not:
- ❌ Start with naming or style
- ❌ Distribute time uniformly across levels
- ❌ Document issues without a pyramid level
- ❌ Spend more than 15% of the time on level 5
Exercise 5: Pyramid under pressure (Hard)
Scenario: It's Friday at 5:45pm. A PR generated by Claude Code needs merge before the 6pm deploy. You only have 5 minutes. The PR modifies the checkout endpoint of your e-commerce.
What do you do? Document your 5-minute plan and justify each decision.
See solution
5-minute plan for a checkout PR:
Minute 0-1: Quick scope
├── How many files? How many lines?
├── Which files are critical? (payments, auth)
└── Are there changes to data models?
Minute 1-4: Checkout security
├── Are there new or modified secrets?
├── Do the payment endpoints keep auth?
├── Is there user input that reaches queries?
├── Are the amounts calculated on the server (not the client)?
└── Is the payment method validated before charging?
Minute 4-5: Critical business logic
├── Is the total calculated correctly?
├── Are discounts/coupons applied correctly?
└── Is the order status handled correctly?
Justification:
- Only levels 1-2. There's no time for edge cases, performance, or style.
- Within security, I prioritize what applies to checkout: payments, auth, amounts.
- If I find something at level 1 → I don't approve. I delay the deploy.
- If level 1 passes → I approve with a note: "Security and basic logic review OK. Complete review pending for Monday."
- I never approve a checkout PR without reviewing security, no matter the pressure.
What I do NOT do:
- ❌ Review variable names
- ❌ Check code formatting
- ❌ Worry about performance
- ❌ Read the tests (no time)
- ❌ Approve without reviewing security "because it's already 6"
Summary
In this capsule you learned:
- The priority pyramid defines what to review first: Security → Logic → Edge Cases → Performance → Style
- Never review a lower level without covering the ones above — a SQL injection matters more than a variable name
- Security is always #1 because it affects your users, not just your application
- Business logic is #2 because AI doesn't understand your business — only you can verify that the code does what you need
- Edge cases are #3 because the difference between "works in a demo" and "works in production" is here
- Performance is #4 and is only reviewed when relevant — not in every PR
- Style is #5 and should never consume more than 10% of your time
- The time distribution changes according to how much time you have: 5 min (only levels 1-2), 15 min (levels 1-3), 30 min (all)
- The pyramid is flexible at the base and rigid at the top — you can skip downward but never upward
Next capsule: AI Code Review Checklist — building your professional checklist of 15+ items.
Additional resources
- OWASP Top 10 - The 10 most common vulnerabilities — your reference for Level 1
- Google — Code Review Speed - How Google balances speed and rigor in code review
- Stripe — Security Best Practices - A security checklist for payment integrations
- FastAPI — Security Tutorial - Correct implementation of auth in FastAPI
- CWE/SANS Top 25 - The 25 most dangerous software errors
- Anthropic — Claude Code Documentation - Official Claude Code documentation
Debugging & Code Review with Claude Code — Module 4, Capsule 02 Claude Code Agentic Development Path — Guide #6 of 11