Module 2: Mental Models for AI Code
Managing an Intern (MIT)
Managing an Intern (MIT)
Capsule overview
Imagine you're assigned a brilliant intern. They know how to program, they work fast, they know many frameworks. But they've never worked at your company, they don't understand your business, and sometimes they generate solutions that "look good" but don't meet the real requirements. What do you do? You don't review every semicolon. You also don't leave them alone. You supervise them: you review the important decisions, you verify the business logic, and you trust that the boilerplate is fine.
That's exactly what you should do with Claude Code. The "Managing an Intern" (MIT) model — popularized by MIT researchers who study how developers work with AI — gives you a framework for deciding what to supervise, what to delegate, and how to review the output. It's the most intuitive of the three mental models because it maps directly to an experience many developers have had: supervising someone junior.
In this capsule you're going to understand the model in depth, map it to your work with Claude Code, and practice applying it to real scenarios.
The Model: Claude Code as a Brilliant Intern
The metaphor
Claude Code is like an intern who:
├── ✅ Knows many languages and frameworks
├── ✅ Works incredibly fast
├── ✅ Generates code that compiles and "works"
├── ✅ Follows standard patterns correctly
├── ✅ Is tireless and doesn't complain
│
├── ❌ Doesn't understand your business
├── ❌ Doesn't know why certain decisions were made
├── ❌ Sometimes invents things that sound plausible
├── ❌ Can't tell the critical from the trivial
└── ❌ Doesn't ask when it should ask
The metaphor works because the solution is the same: supervise what matters, delegate what's routine. A good manager doesn't review every line of an intern's PR. They review the design decisions, the business logic, and the security. They trust that the syntax and formatting are fine.
What a good manager does
A good manager of an intern is neither a micro-manager nor an absent manager. They're in the middle ground:
Micro-manager (inefficient):
- Reviews every variable name
- Questions every import
- Rewrites the intern's code "their way"
- The intern doesn't grow, the manager doesn't scale
Absent manager (dangerous):
- Accepts everything without review
- Assumes "if it compiles, it's fine"
- Gives no feedback or context
- The bugs reach production
Good manager (effective):
- Reviews the design decisions
- Verifies the business logic
- Asks questions: "Why did you choose this approach?"
- Trusts the boilerplate but verifies what's critical
- Gives context the intern doesn't have
Mapping the Model to Claude Code
What to supervise (high-impact decisions)
These are the areas where Claude Code needs your active supervision — just like an intern needs supervision on decisions they can't make alone:
1. Business logic
Claude Code doesn't know your business. It can generate a technically perfect discount calculation, but with the wrong percentages because it doesn't know that premium customers get 25%, not 20%.
# Claude Code generated this
def calculate_shipping(weight: float, destination: str) -> float:
if destination == "domestic":
return weight * 2.5
elif destination == "international":
return weight * 8.0
else:
return weight * 5.0
# Are the prices correct? Only YOU know.
# Is any destination type missing? Only YOU know.
# Is there a discount for high weight? Only YOU know.
The good manager's question: "Are the business values correct?"
2. Architecture decisions
Claude Code makes implicit architecture decisions that may not align with your system:
# Claude Code decided to use an in-memory dict as a "database"
users_db: dict = {}
# Is it appropriate for your case?
# - Quick prototype → Yes, it's fine
# - Production → No, you need real persistence
# - Multiple workers → No, each worker has its own dict
The good manager's question: "Does this architecture decision scale for our case?"
3. Security
Claude Code can generate code that "works" but has vulnerabilities that aren't obvious at first glance:
# Claude Code generated a login endpoint
@app.post("/login")
async def login(username: str, password: str):
user = db.query(User).filter(User.username == username).first()
if user and user.password == password: # ⚠️ Plaintext comparison
return {"token": create_token(user.id)}
raise HTTPException(status_code=401)
The good manager's question: "Is there anything here an attacker could exploit?"
In this case: the passwords are compared in plaintext (they should be hashed), and the login parameters arrive as query params (they should be in the body).
4. Edge cases that matter
Claude Code handles the "happy path" consistently well. The edge cases that cause problems in production are another story:
# Claude Code generated a payment-splitting function
def split_payment(total: float, num_people: int) -> list[float]:
per_person = round(total / num_people, 2)
return [per_person] * num_people
# What happens with num_people = 0? → ZeroDivisionError
# What happens with total = 100.00 and num_people = 3?
# per_person = 33.33
# 33.33 * 3 = 99.99 → 1 cent is missing!
# Who pays the extra cent?
The good manager's question: "What happens with unusual inputs or edge cases?"
What to delegate (routine work)
These are the areas where Claude Code is consistently good and your supervision doesn't add significant value:
Delegate with confidence:
├── Boilerplate and project setup
├── Standard imports
├── Basic framework configuration
├── Pydantic models for simple data
├── Code formatting and style
├── Type hints
├── Descriptive docstrings
├── CRUD endpoints that follow standard patterns
├── File and directory structure
└── Testing framework configuration
This doesn't mean "ignore." It means a quick 30-second visual review is enough. If something catches your attention, go deeper. If not, move on.
The Manager's Toolkit
The good manager's 5 questions
When Claude Code generates code, ask yourself these 5 questions in order. Each one takes seconds and saves you hours of debugging:
1. "Does it do what I asked?"
→ Does the code meet the requirement I described?
→ Is anything I asked for missing?
→ Is there anything I didn't ask for but it added?
2. "Are the business values correct?"
→ Are the numbers, percentages, constants the real ones?
→ Do the conditions reflect the business rules?
3. "Is there anything an attacker could exploit?"
→ Unsanitized inputs?
→ Exposed secrets?
→ SQL queries with concatenation?
4. "What happens when things go wrong?"
→ Does it handle errors?
→ What happens with null, empty, negative?
→ Are the error messages safe?
5. "Is this design decision appropriate for our case?"
→ Does the architecture scale?
→ Are the dependencies the right ones?
→ Are there alternatives we should consider?
Where to look: the efficient review
A good manager doesn't read the intern's entire PR. They know where to look:
ALWAYS look at:
├── Functions that handle money, permissions, or sensitive data
├── Database queries (especially WHERE and JOINs)
├── Authentication and authorization handling
├── User input validations
└── Anything that touches persistent state
REVIEW QUICKLY:
├── Data models (do the fields make sense?)
├── Routes and endpoints (are the paths coherent?)
├── Error handling (is there try/except where there should be?)
└── Constants and configuration (are the values reasonable?)
TRUST (visual review):
├── Imports
├── Type hints
├── Formatting
├── Framework boilerplate
└── File structure
When to intervene
A good manager intervenes when they detect warning signs, not by routine:
Signs that you should intervene:
├── The code does something you didn't ask for
├── There's an import you don't recognize
├── The solution is more complex than expected
├── There's business logic you can't verify
├── Error handling is missing on risky operations
├── There are hardcoded values that should be configurable
└── The approach is unusual for the problem
Complete Example: Applying Managing an Intern
Scenario
You ask Claude Code: "Create an endpoint to register users with email and password."
Claude Code generates:
from fastapi import FastAPI, HTTPException, Depends
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel, EmailStr, Field
from passlib.context import CryptContext
from jose import JWTError, jwt
from datetime import datetime, timedelta
from typing import Optional
import uuid
import os
app = FastAPI(title="User Registration API")
SECRET_KEY = os.getenv("SECRET_KEY", "dev-secret-key-change-in-production")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
users_db: dict = {}
class UserCreate(BaseModel):
email: EmailStr
password: str = Field(..., min_length=8, max_length=128)
full_name: str = Field(..., min_length=1, max_length=200)
class UserResponse(BaseModel):
id: str
email: str
full_name: str
created_at: datetime
class Token(BaseModel):
access_token: str
token_type: str
def hash_password(password: str) -> str:
return pwd_context.hash(password)
def verify_password(plain: str, hashed: str) -> bool:
return pwd_context.verify(plain, hashed)
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15))
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
@app.post("/register", response_model=UserResponse, status_code=201)
async def register_user(user: UserCreate):
if any(u["email"] == user.email for u in users_db.values()):
raise HTTPException(status_code=409, detail="Email already registered")
user_id = str(uuid.uuid4())
new_user = {
"id": user_id,
"email": user.email,
"full_name": user.full_name,
"password_hash": hash_password(user.password),
"created_at": datetime.utcnow(),
}
users_db[user_id] = new_user
return UserResponse(**new_user)
@app.post("/token", response_model=Token)
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = next(
(u for u in users_db.values() if u["email"] == form_data.username),
None,
)
if not user or not verify_password(form_data.password, user["password_hash"]):
raise HTTPException(status_code=401, detail="Invalid credentials")
access_token = create_access_token(
data={"sub": user["email"]},
expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES),
)
return Token(access_token=access_token, token_type="bearer")
async def get_current_user(token: str = Depends(oauth2_scheme)):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
email: str = payload.get("sub")
if email is None:
raise HTTPException(status_code=401, detail="Invalid token")
except JWTError:
raise HTTPException(status_code=401, detail="Invalid token")
user = next((u for u in users_db.values() if u["email"] == email), None)
if user is None:
raise HTTPException(status_code=401, detail="User not found")
return user
@app.get("/me", response_model=UserResponse)
async def read_users_me(current_user: dict = Depends(get_current_user)):
return UserResponse(**current_user)
Review as a good manager
Question 1: "Does it do what I asked?"
I asked for: an endpoint to register users with email and password.
It generated: registration + login + token + /me endpoint
→ It generated MORE than I asked for. Is that a problem?
- If I want a complete auth system → Useful, saves time
- If I want only registration → Extra code to maintain
→ Decision: I keep it, but I note that it added scope
Question 2: "Are the business values correct?"
- min_length=8 for password → Is that my company's minimum?
- max_length=128 for password → Reasonable
- ACCESS_TOKEN_EXPIRE_MINUTES = 30 → Is that appropriate for my app?
- Status code 409 for a duplicate email → Correct (Conflict)
- Status code 201 for registration → Correct (Created)
→ Verify against the real business requirements
Question 3: "Is there anything an attacker could exploit?"
⚠️ SECRET_KEY has a fallback "dev-secret-key-change-in-production"
→ In production, if they forget to set the variable, the secret is public
→ ACTION: Remove the fallback, make it fail if it's not configured
✅ Password is hashed with bcrypt → Correct
✅ Login uses OAuth2PasswordRequestForm (body, not query params) → Correct
✅ Token has expiration → Correct
✅ Token validation handles JWTError → Correct
⚠️ There's no rate limiting on /register or /token
→ An attacker can brute force or spam registrations
→ ACTION: Add rate limiting (can be later)
⚠️ The email isn't normalized to lowercase
→ "User@Email.com" and "user@email.com" would be different users
→ ACTION: Normalize the email
Question 4: "What happens when things go wrong?"
✅ Duplicate email → 409 with a clear message
✅ Invalid credentials → 401 without revealing whether the email exists
✅ Invalid token → 401
⚠️ What happens if users_db is corrupt? → N/A with an in-memory dict
⚠️ There's no logging → In production you'll need logs of auth events
Question 5: "Is this design decision appropriate?"
⚠️ users_db as an in-memory dict
→ Prototype: OK
→ Production: Needs a real database
→ ACTION: If it's a prototype, I accept. If not, I change it.
✅ bcrypt for hashing → Industry standard
✅ jose for JWT → A mature and reliable library
✅ UUID4 for IDs → Correct
✅ Pydantic for validation → Correct
Final result:
Review time: ~10 minutes
Issues found: 3 actionable (secret fallback, email normalization,
rate limiting)
Minor issues: 2 (logging, in-memory storage)
Decision: EDIT — 90% of the code is fine
Anti-Patterns: The Extremes
Anti-pattern 1: The micro-manager
The micro-manager reviews every line of Claude Code's output as if it were code from a stranger in critical production:
Micro-manager:
"Why did it use uuid.uuid4() and not uuid.uuid1()?"
"Why did it put the os import at the end?"
"I prefer CryptContext with schemes=['argon2']"
"I'm going to rewrite all the variable names"
Result:
- 45 minutes reviewing 80 lines of code
- Ended up rewriting 70% of the code
- Would have been faster to write it from scratch
- Didn't find the real issue (the SECRET_KEY fallback)
because they got lost in insignificant details
Why it's an anti-pattern: The micro-manager spends all their time on low-impact decisions and loses sight of the high-impact ones. It's like a manager who corrects the spelling in an intern's email but doesn't check that the intern sent the financial data to the wrong client.
Anti-pattern 2: The absent manager
The absent manager accepts everything without review — "if Claude Code generated it, it'll be fine":
Absent manager:
"Looks good, it compiles, the endpoint responds."
→ Accepts and deploys
Result:
- 0 minutes of review
- The SECRET_KEY fallback reaches production
- There's no rate limiting
- A bot registers 100,000 fake accounts
- 3 weeks later: security incident
Why it's an anti-pattern: The absent manager adds no value to the process. Claude Code doesn't need a manager who only clicks "accept." It needs a manager who provides the context it doesn't have: business rules, security requirements, system limitations.
Anti-pattern 3: The inconsistent manager
The inconsistent manager varies their level of supervision without criteria — sometimes micro-managing, sometimes absent:
Inconsistent manager:
Monday (in a hurry): "Looks good, I accept" → auth without review
Tuesday (relaxed): "I'm going to review every import" → README over-reviewed
Wednesday (paranoid): "I don't trust AI" → rewrites everything manually
Result:
- The critical code (Monday) passed without review
- The trivial code (Tuesday) consumed unnecessary time
- The useful code (Wednesday) was wasted
Why it's an anti-pattern: Inconsistency means your level of supervision depends on your mood, not on the code's risk. This is exactly what the mental models prevent.
Managing an Intern: Levels of Supervision
The MIT model defines 3 levels of supervision that map directly to types of code:
Level 1: Direct supervision
For code where an error is unacceptable.
When:
├── Authentication and authorization
├── Payment processing
├── Handling sensitive data (PII, health, financial)
├── Cryptography and secret management
└── Queries that modify data (UPDATE, DELETE)
How:
├── Read every line
├── Verify against official documentation
├── Write or review tests for each case
├── Ask specific questions: "Why this approach?"
└── Don't accept until you're sure
Level 2: Focused review
For code where an error is problematic but not catastrophic.
When:
├── Non-financial business logic
├── API endpoints with validation
├── Complex read queries
├── Error handling
└── Integrations with external services
How:
├── Review the main logic (not every line)
├── Verify obvious edge cases
├── Confirm that error handling exists
├── Check that the tests cover the main scenarios
└── Accept if the structure and logic are correct
Level 3: Verified trust
For routine code where AI is consistently good.
When:
├── Boilerplate and setup
├── Simple data models
├── Standard CRUD
├── Framework configuration
└── Documentation and type hints
How:
├── 30-60 second visual review
├── Does it look reasonable?
├── Does anything jump out?
├── If everything seems fine → accept
└── If something doesn't "smell" right → promote to Level 2
Connection to the Project
How you'll use Managing an Intern in Module 8
In the capstone project you're going to receive a complete FastAPI codebase with planted problems. Your job is to do a professional code review. Using the MIT model:
- You won't review every line of every file. You'll identify which files need direct supervision (auth, payments), which need focused review (business logic, endpoints), and which need verified trust (models, config).
- You'll prioritize your time. Instead of spending 20 minutes on each file, you'll spend 30 minutes on auth, 10 minutes on business logic, and 2 minutes on config. Your total time is lower and your results better.
- You'll document your process. Just as a manager documents feedback for an intern, you'll document what you found, why it matters, and how to fix it.
Troubleshooting
Problem 1: "I don't know if something is Level 1, 2, or 3"
Cause: Lack of experience classifying code by risk. Solution: Ask yourself: "If this code has a bug and reaches production, who gets called at 3 AM?" If the answer is "no one" → Level 3. If it's "the product team" → Level 2. If it's "the security team and the CEO" → Level 1.
Problem 2: "I end up micro-managing without realizing"
Cause: Inertia — it's easier to review everything than to decide what to review. Solution: Before reviewing, spend 1 minute classifying the code into levels. Write: "Level 1: [files]. Level 2: [files]. Level 3: [files]." Then review in that order. If you catch yourself reviewing the imports of a Level 3 file, stop and move on.
Problem 3: "The output has more than I asked for — do I review all of it?"
Cause: Claude Code sometimes generates more scope than requested. Solution: First decide if you want the extra scope. If not, remove it — don't review it. If yes, classify it into levels and review it at the appropriate level. Don't review something at Level 1 that you didn't ask for and that is Level 3.
Problem 4: "I don't know the domain well enough to evaluate business logic"
Cause: You're working in an area you don't master. Solution: When you don't know the domain, everything goes up one level of supervision. What would be Level 2 becomes Level 1. What would be Level 3 becomes Level 2. If you can't verify the business logic, find someone who can — a product manager, a domain expert, the business documentation.
Exercises
Exercise 1: Classify into levels (Easy)
Classify each block of code into Level 1 (direct supervision), Level 2 (focused review), or Level 3 (verified trust):
- An import of
loggingandsys - A function that calculates sales tax based on the state
- A middleware that verifies JWT tokens on every request
- A Pydantic model with 5 fields for a products endpoint
- A function that sends a welcome email on registration
See solution
- Import of logging and sys → Level 3. Standard Python imports. Visual review.
- Tax calculation → Level 1. Financial business logic. You must verify that the percentages are correct for each state, that it handles tax-free states, and that the calculations are precise.
- JWT middleware → Level 1. Critical security. You must verify that it validates correctly, that it handles expired tokens, that it doesn't expose information in errors.
- Products Pydantic model → Level 3. Boilerplate. Quick visual review: do the fields make sense? Are the types correct?
- Welcome email → Level 2. It's not security-critical, but you need to verify that it doesn't send to the wrong address, that the content is correct, and that it handles send errors.
Exercise 2: Be the manager (Medium)
Claude Code generated this function. Apply the good manager's 5 questions:
from decimal import Decimal
from typing import Optional
from datetime import datetime
def apply_coupon(
subtotal: Decimal,
coupon_code: str,
user_tier: str,
order_date: Optional[datetime] = None,
) -> dict:
coupons = {
"WELCOME10": {"discount": Decimal("0.10"), "min_purchase": Decimal("50.00")},
"SUMMER25": {"discount": Decimal("0.25"), "min_purchase": Decimal("100.00")},
"VIP50": {"discount": Decimal("0.50"), "min_purchase": Decimal("0.00")},
}
if coupon_code not in coupons:
return {"valid": False, "error": "Invalid coupon code"}
coupon = coupons[coupon_code]
if subtotal < coupon["min_purchase"]:
return {
"valid": False,
"error": f"Minimum purchase of ${coupon['min_purchase']} required",
}
if coupon_code == "VIP50" and user_tier != "vip":
return {"valid": False, "error": "This coupon is for VIP members only"}
discount_amount = subtotal * coupon["discount"]
final_total = subtotal - discount_amount
return {
"valid": True,
"original": float(subtotal),
"discount": float(discount_amount),
"total": float(final_total),
"coupon_applied": coupon_code,
}
See solution
Question 1: "Does it do what I asked?" Assuming you asked for a function to apply coupons: yes, it applies coupons with validations. Clear structure.
Question 2: "Are the business values correct?"
- WELCOME10 = 10% with a $50 minimum → Correct for your business?
- SUMMER25 = 25% with a $100 minimum → Correct?
- VIP50 = 50% with no minimum → Really 50%? With no maximum discount cap?
- ⚠️ The coupons are hardcoded. In production they should come from a database.
- ⚠️ There's no coupon expiration date. SUMMER25 works all year.
- ⚠️
order_dateis received but never used.
Question 3: "Is there anything an attacker could exploit?"
- Not directly (no SQL, no auth). But VIP50 with a 50% discount and no cap can be exploited if someone knows the code.
- There's no per-user usage limit.
Question 4: "What happens when things go wrong?"
- Invalid coupon → Handled with
valid: False - Minimum not met → Handled
- ⚠️ What happens with a negative
subtotal? It doesn't validate. - ⚠️ What happens if
user_tierisn't a valid one? It silently allows the coupon (except VIP50).
Question 5: "Is the design decision appropriate?"
- ✅ Uses Decimal for money (correct)
- ⚠️ Returns float in the result — loses Decimal's precision
- ⚠️ Hardcoded coupons — doesn't scale
- The
order_dateparameter exists but isn't used (dead code or an incomplete feature)
Decision: EDIT. The structure is solid (70% OK). Edit: remove the conversion to float, add validation for a negative subtotal, decide what to do with order_date, verify the business values.
Exercise 3: Find the wrong level (Medium)
A developer applies Managing an Intern like this. What's wrong?
Review of a PR generated by Claude Code:
File: auth/jwt_handler.py
- Level: 3 (verified trust)
- Time: 30 seconds
- Result: "Looks good, I accept"
File: models/user.py
- Level: 1 (direct supervision)
- Time: 25 minutes
- Result: "I changed the names of 4 fields"
File: routes/payment.py
- Level: 2 (focused review)
- Time: 5 minutes
- Result: "The logic seems correct"
See solution
The levels are inverted:
- auth/jwt_handler.py should be Level 1 (not 3). JWT handling is critical security. 30 seconds of review for authentication code is dangerously insufficient.
- models/user.py should be Level 3 (not 1). Pydantic data models are boilerplate. 25 minutes changing field names is classic micro-management.
- routes/payment.py should be Level 1 (not 2). Payment processing requires direct supervision. 5 minutes for financial code is insufficient.
Correct result:
- jwt_handler.py → Level 1, 20-30 min
- user.py → Level 3, 1-2 min
- payment.py → Level 1, 15-25 min
The developer spent most of their time (25 min) on the least important thing and almost nothing on the most critical.
Exercise 4: Supervision plan (Hard)
Claude Code is going to generate a complete "notification system" feature with these files:
notifications/
├── models.py (Pydantic models)
├── routes.py (CRUD endpoints)
├── service.py (email-sending logic)
├── templates.py (email templates)
├── permissions.py (who can send to whom)
└── config.py (SMTP configuration)
Before generating, create your "supervision plan": for each file, define the level and justify it.
See solution
| File | Level | Justification | Estimated time |
|---|---|---|---|
| models.py | 3 | Data models, boilerplate. Check that the fields make sense. | 1-2 min |
| routes.py | 2 | CRUD endpoints, but verify validations and auth on the endpoints. | 5-8 min |
| service.py | 2 | Sending logic. Verify error handling (what happens if SMTP fails?), send rate limiting. | 8-12 min |
| templates.py | 3 | Text templates. Visual review that the content is correct. | 1-2 min |
| permissions.py | 1 | Authorization. Who can send to whom is security logic. Verify every condition. | 15-20 min |
| config.py | 2 | SMTP configuration. Verify that secrets aren't hardcoded, that it uses environment variables. | 3-5 min |
Total estimated time: 33-49 minutes
Without the MIT model: Probably 15-20 minutes per file × 6 = 90-120 minutes, with most of the time spent on low-risk files.
With the MIT model: You focus your time on permissions.py (security) and service.py (logic), and pass quickly through models.py and templates.py. Same or better result in half the time.
Exercise 5: Write the manager's questions (Hard)
For this function, write the good manager's 5 questions with specific answers:
import hashlib
import hmac
import time
def verify_webhook(
payload: bytes,
signature: str,
secret: str,
tolerance: int = 300,
) -> bool:
timestamp, sig = signature.split(",")
ts = int(timestamp.split("=")[1])
if abs(time.time() - ts) > tolerance:
return False
signed_payload = f"{ts}.{payload.decode('utf-8')}"
expected_sig = hmac.new(
secret.encode("utf-8"),
signed_payload.encode("utf-8"),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected_sig, sig.split("=")[1])
See solution
Level: 1 (direct supervision). It's security code — webhook verification.
Question 1: "Does it do what I asked?" Yes, it verifies a webhook by comparing an HMAC signature. It includes a time tolerance for replay attacks.
Question 2: "Are the business values correct?"
- 300-second (5-minute) tolerance → Is it appropriate? Stripe uses 300, it's a good default.
- Does the signature format (timestamp,sig) match the webhook provider you're using?
Question 3: "Is there anything an attacker could exploit?"
- ✅ Uses
hmac.compare_digest(timing-safe comparison) → Correct. - ⚠️
hmac.newshould behmac.HMACorhmac.new— in Python it'shmac.new. But wait...hmac.new()doesn't exist in Python. The correct function ishmac.new(), which is an alias, or more commonlyhmac.HMAC()is used directly. Check the documentation. - ⚠️ If
signaturedoesn't have the expected format (no,),split(",")won't raise an error but the values will be incorrect. There's no validation of the format. - ⚠️ If
payload.decode('utf-8')fails with a binary payload → an unhandled crash.
Question 4: "What happens when things go wrong?"
- Malformed signature → crash (doesn't handle the ValueError from split/int)
- Non-UTF-8 payload → crash (UnicodeDecodeError)
- Empty secret → hmac processes it but the result is meaningless
- ⚠️ Missing try/except for an invalid signature format
Question 5: "Is the design decision appropriate?"
- HMAC-SHA256 → Industry standard, correct
- Configurable tolerance → A good design decision
- ⚠️ The signature format assumes a specific provider (Stripe-like). Is it the correct one for your case?
Decision: EDIT. The core logic is correct, but error handling for malformed inputs is missing and there's a possible error with hmac.new.
Summary
In this capsule you learned:
- Managing an Intern is the model that defines how you supervise AI code: like a good manager supervises a brilliant intern
- Claude Code is an intern that knows a lot but doesn't understand your business, can't tell the critical from the trivial, and sometimes invents
- A good manager uses 3 levels of supervision: direct (security, business logic), focused (endpoints, error handling), and verified trust (boilerplate, config)
- The good manager's 5 questions guide you: does it do what I asked?, correct values?, security?, what happens if it fails?, appropriate design?
- The anti-patterns to avoid: micro-manager (you review everything), absent (you review nothing), inconsistent (it depends on your mood)
- The key is to invest supervision time proportional to the risk, not to the volume of code
Next capsule: Circuit Breaker — when to pause and verify before continuing.
Additional resources
- MIT Research — How Developers Use AI - MIT research on how developers supervise AI-generated code
- The Manager's Path — Camille Fournier - The management framework that inspires the MIT model
- Google Engineering Practices — Code Review - How Google scales code review with prioritization
- Anthropic — Claude Code Documentation - Official Claude Code documentation
- OWASP Code Review Guide - A security checklist for code review
- Dan Luu — Developer Productivity - Essays on software productivity that apply to supervising AI
Debugging & Code Review with Claude Code — Module 2, Capsule 02 Claude Code Agentic Development Path — Guide #6 of 11