Module 8: Capstone Project

Phase 2: Debugging — Confirming and Diagnosing the Runtime Problems

Phase 2: Debugging — Confirming and Diagnosing the Runtime Problems

Capsule overview

In the previous phase you did static code review: you read the code, compared it against the requirements, and documented findings. Some of those findings are obvious just by reading the code (a hardcoded secret, an import that doesn't exist). But others need confirmation by running the code: does it really crash with an empty list? Does the filter really return incorrect data? Does the type error really occur with that input?

This capsule guides you through the systematic debugging process from Module 6, applied to the TaskFlow API codebase. You'll set up the project locally, run the API, and confirm each finding that requires runtime evidence. You'll also hunt for bugs that only show up when you execute — the ones you can't find just by reading.


Project Setup

Step 1: Create the file structure

Create a directory for the project and replicate the codebase structure from Capsule 02:

mkdir taskflow-api
cd taskflow-api

mkdir routes
mkdir services

touch main.py config.py database.py models.py requirements.txt
touch routes/__init__.py routes/auth.py routes/tasks.py routes/users.py
touch services/__init__.py services/auth_service.py services/task_service.py

Copy the contents of each file from Capsule 02 into the corresponding file.

Step 2: Install dependencies

python -m venv venv
source venv/bin/activate  # On macOS/Linux
# venv\Scripts\activate   # On Windows

pip install -r requirements.txt

Step 3: Configure environment variables

export JWT_SECRET_KEY="test-secret-for-debugging"
export DATABASE_URL="sqlite:///./taskflow.db"

Step 4: Try to run the application

uvicorn main:app --reload --port 8000

Pay attention to what happens. If the application doesn't start, the error you see is your first confirmed runtime bug. Document it.


What to Expect When You Run It

When you try to run the application, you may hit immediate errors. These are the kinds of startup errors you should expect:

Import errors

ModuleNotFoundError: No module named 'pydantic_settings'
ImportError: cannot import name 'verify_hash' from 'bcrypt'

If the code imports something that doesn't exist in the installed dependencies, you'll get a ModuleNotFoundError or ImportError at startup. Check imports of specific functions too: a package existing doesn't guarantee that every imported function is real.

What to do:

  1. Identify which import fails
  2. Check whether the package is in requirements.txt
  3. Check whether the imported class/function exists in the installed version
  4. Document it as a Hallucination finding
  5. Temporarily fix the import so you can keep debugging the rest

Configuration errors

If the application depends on environment variables you didn't configure, it may fail to start.

What to do:

  1. Read the error to understand which variable is missing
  2. Set the minimum necessary variables
  3. Evaluate whether the application should handle missing configuration better

Systematic Debugging Process

The 5-step process (Module 6)

For each bug you need to confirm, follow this process:

1. REPRODUCE   → Create the exact request that causes the problem
2. ISOLATE     → Identify the exact line or function
3. DIAGNOSE    → Understand WHY the error occurs
4. FIX         → Implement the correction (in Phase 3)
5. VERIFY      → Confirm the fix works (in Phase 3)

In this phase you only run steps 1-3. Steps 4-5 belong to the Correction Phase (Capsule 04).

Debugging tools

To debug the API you need to be able to make requests. These are your options:

Option 1: curl from the terminal

# Health check
curl http://localhost:8000/health

# Register user
curl -X POST http://localhost:8000/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email": "test@example.com", "name": "Test User", "password": "testpassword123"}'

# Login
curl -X POST http://localhost:8000/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "test@example.com", "password": "testpassword123"}'

# Create task (replace TOKEN with the token from login)
curl -X POST http://localhost:8000/tasks/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer TOKEN" \
  -d '{"title": "My first task", "description": "Description", "priority": "high"}'

Option 2: FastAPI Swagger UI

If the application starts correctly, you can use the interactive documentation at:

http://localhost:8000/docs

Swagger UI lets you send requests directly from the browser.

Option 3: httpie (curl alternative)

pip install httpie

# Health check
http GET localhost:8000/health

# Register user
http POST localhost:8000/auth/register \
  email=test@example.com \
  name="Test User" \
  password=testpassword123

Debugging Scenarios

Scenario 1: Verify the registration flow

Goal: Confirm that registration stores the password correctly.

# 1. REPRODUCE: Register a user
curl -X POST http://localhost:8000/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email": "debug@test.com", "name": "Debug User", "password": "securepass123"}'

What to check after registration:

Open the SQLite database and check how the password was stored:

sqlite3 taskflow.db "SELECT email, password FROM users WHERE email = 'debug@test.com'"

Diagnostic questions:

  • Is the password hashed or in plain text?
  • If it's in plain text, where should it be hashed?
  • Does the registration endpoint call hash_password() before inserting?

Follow the complete flow in the code:

routes/auth.py: register() 
  → Does it call hash_password()?
  → What happens to user.password before the INSERT?
  → Is it inserted directly with no transformation?

Scenario 2: Verify pagination

Goal: Confirm that pagination works correctly.

First, create enough tasks to test:

TOKEN="your-token-here"

# Create 15 tasks
for i in $(seq 1 15); do
  curl -s -X POST http://localhost:8000/tasks/ \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer $TOKEN" \
    -d "{\"title\": \"Task $i\", \"priority\": \"medium\"}"
done

Now test pagination:

# Page 1 (should return tasks 1-10)
curl -s "http://localhost:8000/tasks/?page=1&size=10" \
  -H "Authorization: Bearer $TOKEN" | python -m json.tool

# Page 2 (should return tasks 11-15)
curl -s "http://localhost:8000/tasks/?page=2&size=10" \
  -H "Authorization: Bearer $TOKEN" | python -m json.tool

Diagnostic questions:

  • Does page 1 return the first 10 tasks?
  • Does page 2 return the remaining 5?
  • Or is page 1 empty and page 2 has the first 10?
  • What offset formula does the code use?

Review the offset calculation in services/task_service.py:

offset = page * size

For page=1, size=10: offset = 10
→ It skips the first 10 tasks on page 1

Is that correct? What should the formula be so the first page (page=1) starts from offset 0?

Scenario 3: Verify authorization between users

Goal: Confirm that one user can't access another user's tasks.

# Register two users
curl -X POST http://localhost:8000/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email": "user1@test.com", "name": "User 1", "password": "password123"}'

curl -X POST http://localhost:8000/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email": "user2@test.com", "name": "User 2", "password": "password123"}'

# Login with each user
TOKEN1=$(curl -s -X POST http://localhost:8000/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "user1@test.com", "password": "password123"}' | python -c "import sys, json; print(json.load(sys.stdin)['access_token'])")

TOKEN2=$(curl -s -X POST http://localhost:8000/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "user2@test.com", "password": "password123"}' | python -c "import sys, json; print(json.load(sys.stdin)['access_token'])")

# User 1 creates a task
curl -X POST http://localhost:8000/tasks/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN1" \
  -d '{"title": "User 1 secret task", "priority": "high"}'

Now the key test:

# User 2 tries to access User 1's task
# Assuming the task has id=1
curl -s http://localhost:8000/tasks/1 \
  -H "Authorization: Bearer $TOKEN2"

Diagnostic questions:

  • Can User 2 see User 1's task?
  • What HTTP code does it return? 404 or 403?
  • Where is ownership verified in the code?
  • According to RF-05.6, what should it return?

Review services/task_service.py, function get_task_by_id():

  • Does the query filter by user_id?
  • Or does it return the task regardless of who owns it?

Scenario 4: Verify the statistics

Goal: Confirm that statistics are calculated correctly.

# Create tasks with different statuses
curl -X POST http://localhost:8000/tasks/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN1" \
  -d '{"title": "Pending task", "status": "pending"}'

curl -X POST http://localhost:8000/tasks/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN1" \
  -d '{"title": "Completed task", "status": "completed"}'

# Delete a task
curl -X DELETE http://localhost:8000/tasks/1 \
  -H "Authorization: Bearer $TOKEN1"

# Get statistics
curl -s http://localhost:8000/users/stats \
  -H "Authorization: Bearer $TOKEN1" | python -m json.tool

Diagnostic questions:

  • Do the statistics include the deleted task?
  • According to RF-04.2, should they exclude soft-deleted tasks?
  • How does it implement delete? Is it soft delete (change status) or hard delete (remove row)?
  • If it's hard delete, does it violate RF-03.6?

Scenario 5: Verify the statistics with zero tasks

Goal: Confirm that statistics work for a user with no tasks.

# Register a new user with no tasks
curl -X POST http://localhost:8000/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email": "empty@test.com", "name": "Empty User", "password": "password123"}'

TOKEN_EMPTY=$(curl -s -X POST http://localhost:8000/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "empty@test.com", "password": "password123"}' | python -c "import sys, json; print(json.load(sys.stdin)['access_token'])")

# Get statistics without having any tasks
curl -s http://localhost:8000/users/stats \
  -H "Authorization: Bearer $TOKEN_EMPTY"

Diagnostic questions:

  • What response do you get?
  • Do you get a 500 error?
  • If there's an error, what's the stack trace?
  • Where exactly does the error occur?

Hint: review the get_user_stats() function in task_service.py. What happens when total = 0?

Scenario 6: Test the search endpoint

Goal: Confirm whether there are vulnerabilities in the user search endpoint.

# Normal search
curl -s "http://localhost:8000/users/search?query=Test"

# SQL injection attempt
curl -s "http://localhost:8000/users/search?query=test'%20OR%20'1'='1"

# Data extraction attempt
curl -s "http://localhost:8000/users/search?query=test'%20UNION%20SELECT%20password,email,name%20FROM%20users--"

Diagnostic questions:

  • Does the endpoint require authentication?
  • Is the search vulnerable to SQL injection?
  • Can an attacker extract passwords with UNION SELECT?
  • What should this endpoint have: authentication and parameterized queries?

Scenario 7: Test the input validations

Goal: Confirm that the validations match the requirements.

# Try to register with a short password (RF-01.4 says 8 characters minimum)
curl -X POST http://localhost:8000/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email": "weak@test.com", "name": "Weak", "password": "1234"}'

# Try to create a task with a 1-character title (RF-05.1 says minimum 3)
curl -X POST http://localhost:8000/tasks/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN1" \
  -d '{"title": "A", "priority": "medium"}'

# Try to create a task with an invalid priority (RF-05.2)
curl -X POST http://localhost:8000/tasks/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN1" \
  -d '{"title": "Test task", "priority": "urgent"}'

# Try pagination with page=0 (RF-05.4 says page >= 1)
curl -s "http://localhost:8000/tasks/?page=0&size=10" \
  -H "Authorization: Bearer $TOKEN1"

Diagnostic questions:

  • Does the system accept a 4-character password? Should it?
  • Does the system accept a 1-character title? Should it?
  • Does the system accept a priority of "urgent"? Should it?
  • Does the system accept page=0? Should it?

Scenario 8: Test the soft delete

Goal: Confirm that delete works according to the requirements.

# Create a task
curl -X POST http://localhost:8000/tasks/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN1" \
  -d '{"title": "Task to delete", "priority": "low"}'

# Note the ID of the created task, for example id=5

# Delete the task
curl -X DELETE http://localhost:8000/tasks/5 \
  -H "Authorization: Bearer $TOKEN1"

# Check in the database
sqlite3 taskflow.db "SELECT * FROM tasks WHERE id = 5"

Diagnostic questions:

  • Does the task still exist in the database with status "deleted"?
  • Or was it completely removed (hard delete)?
  • According to RF-03.6, what should happen?

Using Claude Code for Debugging

When to use Claude Code

Claude Code is useful during debugging to:

1. Interpret stack traces

If you get a 500 error, copy the stack trace and pass it to Claude Code:

I'm debugging this FastAPI API. When I do GET /users/stats
with a user that has no tasks, I get this error:

[paste the full stack trace]

What's causing this error?

Claude Code can quickly identify the exact line and the error type. But remember: verify its diagnosis by looking at the code yourself.

2. Verify whether an import/API exists

Does the BaseSettings class exist in the pydantic_settings package?
Or is it in pydantic directly? I'm using pydantic 2.9.0.

3. Understand SQLite behavior

In SQLite, if I call dict() on a sqlite3.Row, 
do I get a dictionary with the columns as keys?

When NOT to use Claude Code

  • ❌ "Debug this API for me" — the debugging is your exercise
  • ❌ "Are there bugs in this code?" — that's what you're determining
  • ❌ Accepting a diagnosis without verifying yourself that it's correct

How to document your use of Claude Code

For every time you use Claude Code during debugging, document:

## Claude Code Usage #1

**Question:** [What I asked]
**Answer:** [Summary of its answer]
**Verification:** [How I verified the answer is correct]
**Result:** [Correct / Partially correct / Incorrect]

This documentation is part of your process and is graded in the "Process" section (20% of the evaluation).


Debugging Documentation Template

For each runtime bug you confirm, document the complete process:

# Debugging Log — TaskFlow API

## Bug #1: [Descriptive title]

### 1. Reproduce
- **Request:** [curl command or request description]
- **Expected response:** [What should happen according to the requirements]
- **Actual response:** [What actually happened]
- **Error/Output:** [Stack trace or incorrect response]

### 2. Isolate
- **File:** [Where the problem lives]
- **Function:** [Function name]
- **Line(s):** [Line number(s)]
- **Execution flow:** [How execution reaches this point]

### 3. Diagnose
- **Root cause:** [Why the error occurs]
- **Why static code review didn't catch it:** [Explanation]
- **Category:** [Runtime / Edge Case / Logic / etc.]

### 4. Proposed fix
[Implemented in the Correction Phase — Capsule 04]

---

## Bug #2: [Descriptive title]
[Same format]

Manual Debugging vs Claude Code

When manual debugging is necessary

There are situations where Claude Code can't help effectively:

1. Database state

Claude Code can't see your database. You need to inspect it directly:

# See the table structure
sqlite3 taskflow.db ".schema"

# See the stored data
sqlite3 taskflow.db "SELECT * FROM users"
sqlite3 taskflow.db "SELECT * FROM tasks"

# Check whether passwords are hashed
sqlite3 taskflow.db "SELECT email, password FROM users"

# Check whether the delete is soft or hard
sqlite3 taskflow.db "SELECT * FROM tasks WHERE status = 'deleted'"

2. Live endpoint behavior

Claude Code can't run your API. You need to:

  • Make the real request
  • Observe the real response
  • Compare against what's expected

3. Interaction between components

Some bugs only show up when components interact. For example, the tasks endpoint calls the service which calls the database. A bug could be that the query is correct but the service misinterprets the result. That requires manual tracing.

When Claude Code helps most

1. Interpreting cryptic errors

# This error isn't obvious:
# TypeError: 'NoneType' object is not subscriptable

# Claude Code can explain that you're trying to 
# access an index or key of a None value

2. Verifying APIs and signatures

"Does jwt.encode() in PyJWT 2.9 return str or bytes?"
"Does bcrypt.hashpw() require bytes or accept str?"

3. Suggesting diagnostic hypotheses

When you have an error but don't know where to start, Claude Code can suggest the 3 most likely causes. But verify each one yourself.


Complete Debugging Checklist

Before moving on to the Correction Phase, verify that you've run these scenarios:

Authentication flow

  • Register a user and verify how the password is stored
  • Login with correct credentials
  • Login with incorrect credentials
  • Access a protected endpoint with a valid token
  • Access a protected endpoint without a token

Tasks flow

  • Create a task with valid data
  • Create a task with an empty or very short title
  • Create a task with an invalid priority
  • List tasks with pagination (page=1, page=2)
  • Get the detail of your own task
  • Try to get the detail of another user's task
  • Update your own task
  • Delete a task and verify whether it's soft or hard delete

Statistics

  • Get statistics with several tasks
  • Get statistics with zero tasks
  • Verify that statistics exclude deleted tasks

Security

  • Test SQL injection in user search
  • Verify that the search endpoint requires auth
  • Test SQL injection in task filters

Validations

  • Password shorter than 8 characters
  • Title shorter than 3 characters
  • Page 0 or negative
  • Invalid priority or status

Scenario 9: Verify the Complete Update Flow

Goal: Confirm that updating tasks works correctly with all the fields.

# Create a task
TASK=$(curl -s -X POST http://localhost:8000/tasks/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN1" \
  -d '{"title": "Original task", "priority": "low", "status": "pending"}')

echo $TASK
TASK_ID=$(echo $TASK | python -c "import sys, json; print(json.load(sys.stdin)['id'])")

# Update only the title
curl -s -X PUT http://localhost:8000/tasks/$TASK_ID \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN1" \
  -d '{"title": "Updated task"}'

# Update only the priority
curl -s -X PUT http://localhost:8000/tasks/$TASK_ID \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN1" \
  -d '{"priority": "high"}'

# Verify that updated_at was updated
curl -s http://localhost:8000/tasks/$TASK_ID \
  -H "Authorization: Bearer $TOKEN1" | python -m json.tool

Diagnostic questions:

  • Is only the sent field updated, or are all of them overwritten?
  • Is the updated_at field updated?
  • What happens if you send a field with an invalid value (e.g., priority "urgent")?

Scenario 10: Verify the pydantic_settings Import

Goal: Confirm whether the pydantic_settings import causes a startup error.

# Try importing it directly to check
python -c "from pydantic_settings import BaseSettings"

Diagnostic questions:

  • Does the import fail with ModuleNotFoundError?
  • Is the pydantic-settings package in requirements.txt?
  • Does the application manage to start despite this import?
  • Is BaseSettings used anywhere in the main.py file?

If the import fails, the error will appear when you try to run uvicorn main:app. This is a finding that's confirmed in seconds but that many people overlook in static code review.

Scenario 11: Verify the verify_hash Import in bcrypt

Goal: Confirm whether verify_hash exists in the bcrypt package.

python -c "from bcrypt import verify_hash"

Diagnostic questions:

  • Does the import fail with ImportError?
  • Does the verify_hash function appear in the bcrypt documentation?
  • Is verify_hash used anywhere in the code, or is it only imported?
  • Which bcrypt function actually does that job? (checkpw)

This is a subtler kind of hallucination than the pydantic_settings one: the bcrypt package does exist and is installed, but the verify_hash function doesn't exist in its API. Claude Code sometimes "invents" functions inside real packages.

Scenario 12: Verify cursor.fetchall(as_dict=True)

Goal: Confirm whether fetchall() accepts the as_dict parameter.

import sqlite3
conn = sqlite3.connect(":memory:")
cursor = conn.cursor()
cursor.execute("SELECT 1")
cursor.fetchall(as_dict=True)  # TypeError?

Diagnostic questions:

  • Does fetchall() on a sqlite3.Cursor accept parameters?
  • What TypeError do you get when you pass as_dict=True?
  • How do you get results as dictionaries in sqlite3? (conn.row_factory = sqlite3.Row)

This hallucination is particularly dangerous because it doesn't fail at startup — it only fails when the get_tasks() function runs with real results.

Scenario 13: Verify the Task Filters with SQL Injection

Goal: Confirm whether the task filters are vulnerable to SQL injection.

# Normal status filter
curl -s "http://localhost:8000/tasks/?status=pending" \
  -H "Authorization: Bearer $TOKEN1"

# SQL injection attempt via status
curl -s "http://localhost:8000/tasks/?status=pending'%20OR%20'1'='1" \
  -H "Authorization: Bearer $TOKEN1"

# SQL injection attempt via priority
curl -s "http://localhost:8000/tasks/?priority=high'%20UNION%20SELECT%20*%20FROM%20users--" \
  -H "Authorization: Bearer $TOKEN1"

Diagnostic questions:

  • Does the filter with SQL injection return more results than the normal filter?
  • Are the status and priority parameters inserted with ? placeholders or with f-strings?
  • Is this a different finding from the SQL injection in the user search?

How to Interpret the Results

The request returns 200 but with incorrect data

This is the hardest type of bug to find. The endpoint doesn't crash, doesn't return an error — it simply returns data that isn't correct. Examples:

  • Pagination returns the wrong tasks on each page
  • Statistics include tasks they shouldn't include
  • A user can see another user's tasks

To catch these bugs, you need to know what the correct response is and compare it against the actual response. That's why the functional requirements are your main tool.

The request returns 500 (Internal Server Error)

A 500 error indicates a crash on the server. The stack trace will appear in the console where you ran uvicorn. Copy the full stack trace — it contains:

  1. The exact line where the error occurred
  2. The exception type (TypeError, ZeroDivisionError, etc.)
  3. The full traceback showing how that line was reached
# To see the stack trace, look at the terminal where uvicorn is running
# 500 errors are printed to the console automatically

The request returns 422 (Validation Error)

A 422 indicates that FastAPI rejected the input because it doesn't comply with the Pydantic models. This may be correct (validation working) or incorrect (validation too strict or too lax).

Check: should the input be rejected according to the requirements? If yes, the validation works. If not, the validation has a problem.

The request returns 401, or doesn't return 401 when it should

Endpoints that return 401 with no token: correct. Endpoints that return data with no token: incorrect if they should require authentication.


Self-assessment Questions

Before moving on to the Correction Phase, answer these questions:

  1. How many bugs did you find with static code review alone (Phase 1)?
  2. How many additional bugs did you find by running the code (Phase 2)?
  3. Are there Phase 1 findings that turned out to be false positives when tested?
  4. Are there runtime bugs you hadn't caught in the code review?
  5. How many times did you use Claude Code? Was it useful? Did you verify its answers?
  6. Which debugging scenario was the most revealing? Why?
  7. Was there any bug that surprised you when you confirmed it?

These questions will feed your final retrospective (Capsule 05).


Common Mistakes in This Phase

Mistake 1: Not actually running the code

Reading the scenarios and "deducing" what would happen is not the same as running and observing. The surprise you get when you see an error you didn't expect is part of the learning.

Mistake 2: Only testing the happy path

If you only test with valid inputs, you'll confirm that "it works" — but you won't find the edge cases. Test with empty, negative, excessive, and malicious inputs.

Mistake 3: Not documenting while you debug

It's tempting to debug everything and document at the end. But by the end you forget details: which exact request caused the error, what the stack trace was, what you verified. Document in real time.

Mistake 4: Fixing bugs before documenting all of them

Resist the temptation to fix each bug as soon as you find it. Document them all first (this phase), prioritize (next phase), and then fix in order.

Mistake 5: Not resetting the database between tests

If you run many tests, data accumulates and can interfere. For each clean scenario:

rm taskflow.db
# Restart the application to recreate the DB

Mistake 6: Not saving the commands you ran

The curl commands you used to reproduce each bug are evidence of your process. Save them in your debugging log. When you write the justifications, you'll need to remember exactly which request you made and which response you got.

Mistake 7: Ignoring server warnings

Beyond 500 errors, uvicorn may print warnings that don't cause a crash but do indicate problems. DeprecationWarning, for example, can signal use of obsolete APIs. Read the server console, not just the HTTP responses.


Debugging with Prints vs Debugging with Claude Code

When to use print statements

Sometimes the fastest way to understand what's going on is to add a temporary print:

def get_tasks(user_id, status=None, priority=None, page=1, size=10):
    # Temporary debugging
    print(f"DEBUG get_tasks: page={page}, size={size}")
    
    offset = page * size
    print(f"DEBUG computed offset: {offset}")
    
    # ... rest of the code

Prints are useful when:

  • You need to see the value of a variable at a specific point
  • Claude Code can't see your application's state at runtime
  • You want to confirm which branch of an if/else runs

Remember to remove the prints when you finish debugging.

When to use Claude Code

Claude Code is useful when:

  • You have a long, confusing stack trace
  • You don't know where to start looking
  • You need to verify whether an API or import exists
  • You want a second opinion on a hypothesis

Example of an effective question to Claude Code:

When I run GET /users/stats for a user with no tasks,
I get ZeroDivisionError in services/task_service.py line 118.

The line is:
completion_percentage = by_status.get("completed", 0) / total * 100

Why does it fail and what would the correct fix be?

Example of an ineffective question:

My API doesn't work, fix it.

The difference: the first gives specific context, the second delegates the whole job.


Summary of the Debugging Phase

By the end of this phase you should have:

  1. Updated findings document — with the code review findings confirmed and additional runtime bugs
  2. Debugging log — documentation of the debugging process for each runtime bug
  3. Prioritized list — findings ordered by severity, ready for the correction phase
  4. Notes on Claude Code usage — when you used it, what you asked, whether it was useful

Your findings document should now have between 12 and 18 findings. If you have fewer than 12, there are areas you didn't explore enough. Review the debugging checklist and run the scenarios you're missing.


Next capsule: Correction Phase — How to prioritize, implement, and justify each fix, deciding when to regenerate with Claude Code vs edit manually.


Debugging & Code Review with Claude Code — Module 8, Capsule 03 Claude Code Agentic Development Path — Guide #6 of 11