Claude in VS Code: Pair Programming
Chapter 8 · Real-World Workflow — A Feature Built Start to Finish
The previous seven chapters covered individual techniques in isolation: explanation, refactoring, code generation, debugging, testing, cross-file changes. This final chapter puts them together — a complete feature from ticket to commit, with Claude as the pair programmer throughout. The goal is to show how the techniques connect in practice, and what the rhythm of real AI-assisted development looks like.
The Feature: Password Reset via Email
We'll build a password reset flow for a FastAPI web application. It's a realistic, self-contained feature that touches several layers: database schema, service logic, API endpoint, email dispatch, and tests. Here's the ticket:
Add password reset via email
Description
Users should be able to reset their password via a tokenised email link. The flow: request reset → receive email → click link → set new password.
Acceptance criteria
- POST /auth/password-reset-request accepts an email; always returns 200 (no user enumeration)
- A unique, time-limited token is stored against the user record
- A reset email is sent with a link containing the token
- POST /auth/password-reset accepts token + new password; invalidates the token on use
- Expired or already-used tokens return 400
Phase 1 — Orient and Plan
Phase 1 · Explanation
Understand the existing codebase before writing anything
I'm about to add a password reset feature (PROJ-247). Before I start, help me understand the existing auth setup. Open files: models/user.py, routes/auth.py, services/auth_service.py. What does the current auth flow look like, and what patterns should I follow for the new feature?
Claude reads the three files and summarises: the User model fields, how existing routes are structured, how the auth service handles tokens, and what email-sending utilities already exist. This takes two minutes and prevents an hour of wrong-direction work.
Phase 1 · Planning
Ask Claude to produce a change plan
Based on what you've seen, what changes will this feature require? List: new DB columns or tables, new service methods, new routes, new tests, and any existing code that needs updating. Don't write any code yet.
Claude produces a list: add reset_token and reset_token_expires to User, write AuthService.generate_reset_token() and AuthService.consume_reset_token(), add two new routes, update the email service, write tests. Review and adjust the plan before proceeding.
Phase 2 — Schema and Migration
Phase 2 · Generation
Generate the database migration
Write an Alembic migration to add `reset_token` (String, nullable, unique) and `reset_token_expires` (DateTime, nullable) to the users table. Follow the style of the existing migrations in alembic/versions/. Include both upgrade() and downgrade().
Claude generates the migration file. Open an existing migration first so Claude mirrors your exact Alembic style. Review: check the column types match your DB, the constraint name doesn't clash, and downgrade() reverses the change cleanly.
Phase 3 — Service Logic
Phase 3 · Generation
Generate the service methods
Add two methods to AuthService in services/auth_service.py:
1. `generate_reset_token(email: str) -> None` — looks up the user, generates a secure token (secrets.token_urlsafe), sets reset_token and reset_token_expires (now + 1 hour), saves, then calls email_service.send_reset_email(). Returns None regardless of whether the email exists.
2. `consume_reset_token(token: str, new_password: str) -> None` — finds the user by token, raises ValueError("invalid or expired token") if not found or expired or already used, hashes and sets the new password, clears both token fields, saves.
Follow the existing method style in the file.
Providing the full method spec eliminates ambiguity. Claude writes both methods. Review: check the token expiry comparison uses timezone-aware datetimes if your project does, and that the password hashing uses the same utility as the rest of the auth service.
Phase 4 — API Routes
Phase 4 · Generation
Generate the two new endpoints
Add two routes to routes/auth.py following the existing pattern:
- POST /auth/password-reset-request: accepts `{"email": str}`, calls auth_service.generate_reset_token(), always returns 200 with `{"message": "If that email exists, a reset link has been sent"}`.
- POST /auth/password-reset: accepts `{"token": str, "new_password": str}`, calls auth_service.consume_reset_token(), returns 200 on success, catches ValueError and returns 400 with the error message.
Don't add rate limiting — that's handled by middleware.
The constraint about rate limiting is important — Claude would otherwise add it. After generation, manually verify the routes are registered in the router and the request/response models match your Pydantic conventions.
Phase 5 — Debugging a Problem
After wiring the routes up and running the app, the first manual test fails. The reset email sends but the token lookup returns 404.
Phase 5 · Debugging
Diagnose the failure with Claude
I'm getting a 404 when calling /auth/password-reset with a valid token. The token was definitely saved — I can see it in the DB. Here's the consume_reset_token method and the full traceback. What could cause the token lookup to fail even when the token exists in the database?
Claude diagnoses: the token was saved with a trailing newline from secrets.token_urlsafe() being stored via a form field that didn't strip whitespace. The fix: .strip() on both store and lookup. A five-minute diagnosis that would have taken much longer without the static code analysis.
Phase 6 — Tests
Phase 6 · Testing
Generate the test suite
Write pytest integration tests for the two new endpoints using the test client. We use a real test DB (see conftest.py for fixtures). Test cases needed:
- reset-request: valid email returns 200, non-existent email also returns 200 (no enumeration)
- reset: valid token sets new password and clears token fields, expired token returns 400, already-used token returns 400, malformed token returns 400
Name tests as test_[endpoint]_[condition]. Follow the pattern in tests/test_auth.py.
Six test cases specified explicitly. Claude generates them using the existing fixture pattern. Run the suite immediately — fix any import errors or fixture mismatches before declaring done.
Ask Claude for the edge cases you missed
After writing the specified tests, ask:
"What edge cases for this feature haven't I tested yet?" Claude flagged two more: a token generated for a deleted user, and a race condition where two reset requests are made in quick succession. Both worth adding.
Phase 7 — Review Before Commit
Phase 7 · Review
Final review pass with Claude
Before I commit this feature, review the new code for: security issues, missing error handling, anything inconsistent with the existing codebase, and anything I might have missed from the ticket requirements.
Claude flags one issue: the token is logged at DEBUG level in a middleware, which would expose it in log files. Adds a redaction. This is the kind of subtle security issue that code review catches — and Claude catches it here before it ever reaches a reviewer.
The Rhythm of AI-Assisted Development
Looking back at the full workflow, a pattern emerges:
🗺️
Orient first
Read before writing. Ask Claude to explain the existing code and plan the change before generating anything.
📋
Spec before generate
Tell Claude exactly what you want — signature, behaviour, constraints. Vague prompts produce vague code.
🔍
Review every diff
Read every change before accepting it. The diff view is your safety net — use it every time.
🐛
Diagnose, then fix
When something breaks, ask Claude to explain the cause before applying a fix. Understanding prevents recurrence.
🧪
Test as you go
Run tests after each phase, not just at the end. Failures are easier to attribute to a specific change.
🔒
Always review security
Ask Claude for a security pass before committing anything that handles auth, input, or external data.
What Claude Does Not Replace
- ⚠Your judgement. Claude generates options and suggestions. You decide what to accept, what to reject, and what to change. Never commit code you haven't read and understood.
- ⚠Domain knowledge. Claude doesn't know your business rules, your users, or your production constraints. You bring that; Claude handles the mechanical implementation.
- ⚠Runtime verification. Tests pass, code reads correctly — but the only way to know the feature works end-to-end is to run it. Claude can't do that for you.
- ⚠Architecture decisions. Claude can tell you where a change fits within existing patterns. Deciding whether those patterns are right for the next stage of the product is a human call.
- ⚠Accountability. When the code ships and something goes wrong, it's your name on the commit. Claude is the tool; you are the engineer.
🎓
Claude in VS Code: Pair Programming — Complete
8 chapters · From setup to shipping a real feature with Claude