Generating code

Claude in VS Code: Pair Programming

Chapter 4  ·  Generating Code — Features, Boilerplate, and Schema

Generating new code is where Claude feels closest to having a second developer next to you. Describe what you need, and Claude writes it — functions, classes, API routes, database migrations, configuration files. But generated code that doesn't fit your existing patterns creates more work than it saves. This chapter is about giving Claude enough context to generate code that slots in cleanly, and what to check before you commit it.

The Generation Problem: Context Mismatch

Claude's biggest weakness when generating code isn't correctness — it's fit. It can write a perfectly valid function that uses the wrong naming convention, imports a library you don't use, handles errors in a style inconsistent with the rest of the codebase, or reinvents a utility you already have.

This happens because Claude is generating from a description rather than from deep knowledge of your specific codebase. The fix is always the same: give Claude more context about your patterns before asking it to generate.

What Context to Provide

Must have
An example of a similar existing thing. "Here's a function that does something similar — follow the same pattern." This single piece of context eliminates most style and convention mismatches. Open the example file before generating.
Must have
The exact signature or interface. If you know what parameters the function should accept and what it should return, say so explicitly. Don't leave it for Claude to invent.
Good to have
The tech stack and version. "This is a FastAPI 0.111 project using SQLAlchemy 2.0 async." Claude knows many frameworks but defaults change between versions — specificity avoids deprecated patterns.
Good to have
Error handling style. "We raise custom exceptions; we never return None on failure" or "errors are returned as result tuples." Claude will otherwise pick whatever convention seems most common for the language.
Optional
What not to use. "Don't add logging — we have a decorator for that." "Don't use requests — we use httpx." Negative constraints prevent Claude from adding things you'll just have to remove.

Anatomy of a Good Generation Prompt

Example: generating a new API endpoint
GoalWrite a POST /users/password-reset endpoint.

ContextThis is a FastAPI project. See routes/users.py for the pattern — all routes use the `get_db` dependency, return Pydantic response models, and raise HTTPException on error. The User model is in models/user.py.

PatternFollow the same structure as the existing POST /users/verify-email route.

ConstraintsAccept `email: str` in the request body. Return a 200 with `{"message": "reset email sent"}` whether or not the email exists (don't leak user enumeration). Don't add rate limiting — that's handled by middleware.

Four components: what to build, where it lives and what patterns to follow, a concrete example to mirror, and constraints that prevent common additions you don't want. Every generation prompt benefits from all four.

Common Generation Use Cases

New function from description
Write a function `calculate_late_fee(due_date, paid_date, daily_rate)` that returns the total fee as a Decimal. Return 0 if paid on time. Follow the style in billing/utils.py.
Always open a related file first so Claude can see your conventions.
Boilerplate / scaffolding
Create a new service class for sending SMS notifications. Follow the same pattern as services/email_service.py — same constructor, same error handling, same logging approach.
Point Claude at an existing service to mirror. Boilerplate is where "follow this pattern" saves the most time.
Database schema / migration
Write an Alembic migration to add a `password_reset_tokens` table. Columns: id (UUID pk), user_id (FK to users.id), token (string, unique), expires_at (datetime), used_at (nullable datetime).
Specify every column and constraint. Claude will fill in the Alembic boilerplate — you specify the schema.
Configuration / infra files
Write a docker-compose.yml for this project. Services: FastAPI app (port 8000), PostgreSQL 16, Redis. Include a .env reference for DB credentials. Don't expose Redis externally.
Config generation is one of Claude's strongest areas — it knows the formats well and rarely hallucinates field names.
Data transformation / mapper
Write a function that maps a raw Stripe webhook payload dict to our internal `PaymentEvent` dataclass. Only extract the fields we need: id, type, amount, currency, customer_id, created_at.
Give Claude both the input shape and the target type. It handles the mapping; you verify the field names are right.
CLI / script
Write a Python script that reads a CSV of user emails from stdin and calls our `/admin/users/bulk-deactivate` API endpoint in batches of 50. Use httpx. Print progress to stderr.
Scripts are low-risk to generate — they're standalone, easy to test, and the blast radius of a bug is limited.

Reviewing Generated Code Before Committing

Generated code should always be read before it's committed — not just run. Claude is confident and fluent, which means bugs read as naturally as correct code. A review pass catches the categories of problem that tests often miss:

  • Security: does it handle untrusted input? SQL injection, XSS, path traversal, insecure defaults — Claude sometimes omits these unless you asked for them explicitly.
  • Error handling: what happens when the external call fails, the DB is unavailable, or the input is malformed? Verify Claude's error paths match your project's approach.
  • Imports: does the generated code import libraries you actually have? Claude occasionally imports a package that would solve the problem but isn't in your project.
  • Naming: do the identifiers match your conventions? Variable names, function names, and class names should be consistent with the surrounding code.
  • Edge cases: does it handle empty input, None values, zero, negative numbers, or whatever the relevant boundary conditions are for this function?
  • Hallucinated APIs: verify any method or attribute calls against documentation or the actual library source. Claude occasionally invents plausible-sounding method names that don't exist.
  • Hardcoded values: config values, magic numbers, and hardcoded strings that should be constants or environment variables.
Run it, don't just read it
Reading the code is necessary but not sufficient. Run the generated code against real inputs before merging. Type checking and linting catch a class of bugs that reading misses; tests catch behavioural bugs that the linter misses. All three layers together give you reasonable confidence.

Iterating When the First Output Isn't Right

The first generation is rarely perfect. The efficient approach is targeted follow-up, not regenerating from scratch:

Identify the specific problem. Don't say "this isn't right." Say "the error handling on line 12 returns None — it should raise a ValueError with the message 'invalid date range'."
Lock in what's correct. "The function signature and the happy-path logic are correct — only fix the error handling." Prevents Claude from helpfully rewriting parts that were fine.
Add the missing constraint. If Claude got something wrong, it's usually because a constraint was absent from the original prompt. Add it explicitly: "We never return None from this layer — always raise."
Ask for the minimal change. "Make the minimum edit to fix this — don't restructure anything else." Keeps the diff small and the review fast.
If it keeps going wrong, provide an example. "Here's how we handle this in the payment module — do the same." A concrete example beats any amount of description for style and convention alignment.
Generate, review, test in small units
Generate one function at a time rather than asking Claude to write an entire feature in one go. Small generations are faster to review, easier to test, and when something is wrong you know exactly which piece caused it. Long generations that are 80% correct leave you doing a line-by-line review of everything to find the 20%.
Next — Chapter 5: Debugging with Claude
Pasting errors and stack traces, reading runtime failures, and finding root causes. How to give Claude the right diagnostic information, the difference between asking Claude to fix a bug versus explain what caused it, and when to reach for the debugger instead.