Reading Tracebacks
James runs the SmartNotes statistics module that Claude Code generated in their last session. The terminal fills with red text:
Traceback (most recent call last):
File "smartnotes_buggy.py", line 42, in reading_time_seconds
minutes: float = str(words) / words_per_minute
~~~~~~~~~~~^~~~~~~~~~~~~~~~~~
TypeError: unsupported operand type(s) for /: 'str' and 'int'
He stares at it. "That's not an answer. That's an error report."
"Exactly," Emma says. "And most beginners do the worst possible thing with it: they copy-paste the entire wall of text back into the AI and say 'fix this.' That sometimes works. But you learn nothing, and the AI might introduce a new bug while fixing the old one."
She pulls her chair closer. "A traceback is diagnostic information. It tells you three things: what went wrong, where it happened, and how Python got there. You just have to read it in the right order."
When you encounter new Python syntax in this chapter, use the PRIMM-AI+ method from Chapter 42: Predict what the code does before running it [AI-FREE]. Rate your confidence (1-5). Run it to check your prediction. Investigate any surprises. This works for every new concept you'll meet here.
A traceback is the error report Python prints when code crashes. It shows the path Python followed before hitting the error. You read it bottom-up: the error type and message are at the bottom, and the chain of function calls that led there is above.
Python tracebacks read bottom-up (most recent call last). The exception type and message are at the bottom. Each frame shows file, line number, function name, and the source line. The ^ markers in Python 3.12+ highlight the exact expression that failed.
The Bottom-Up Rule
Most people read top to bottom. Tracebacks reward the opposite. Look at the last line first, because that is where Python tells you what broke:
TypeError: unsupported operand type(s) for /: 'str' and 'int'
This line has two parts:
| Part | What it tells you | In this example |
|---|---|---|
| Error type | The category of problem | TypeError -- wrong data type |
| Error message | The specific detail | Can't divide a str by an int |
Before you look at anything else, you already know: something tried to divide a string by an integer. That is enough to start thinking about where.
Reading the Call Chain
Now look one line up. The frame directly above the error message shows where the crash happened:
File "smartnotes_buggy.py", line 42, in reading_time_seconds
minutes: float = str(words) / words_per_minute
~~~~~~~~~~~^~~~~~~~~~~~~~~~~~
Three pieces of information:
| Piece | What it tells you | In this example |
|---|---|---|
| File | Which .py file | smartnotes_buggy.py |
| Line | Which line number | 42 |
| Function | Which function was running | reading_time_seconds |
The source line is printed right below, and the ^ markers point at the exact operation that failed: str(words) / words_per_minute. The left side of the / is a string (because str() converted it), and the right side is an integer. Python cannot divide a string by a number, so it crashes.
Open smartnotes_buggy.py in your editor and go to line 42. You will see that same line. The fix: remove str() so words stays an integer.
The Full Anatomy
Here is the complete traceback from James's crash, labeled:
Traceback (most recent call last): # ← Header (always the same)
File "smartnotes_buggy.py", line 42, in reading_time_seconds
minutes: float = str(words) / words_per_minute
~~~~~~~~~~~^~~~~~~~~~~~~~~~~~
TypeError: unsupported operand type(s) for /: 'str' and 'int'
In longer programs, there can be multiple frames stacked between the header and the error. Each frame is one step in the chain of function calls. The bottom frame is always where the crash actually happened. The frames above it show how Python got there.
For now, you only need the bottom two lines: the frame (file, line, function) and the error (type, message). As your programs grow, you will trace through multiple frames.
Four Error Types You Already Know
You have seen error messages in earlier chapters, even if you did not study them closely. Here are four types that show up constantly in AI-generated code:
| Error type | What it means | Example trigger |
|---|---|---|
TypeError | Wrong data type for an operation | "hello" / 5 (can't divide string by int) |
KeyError | Accessing a dictionary key that does not exist | data["rating"] when data has no "rating" key |
ValueError | Right type, wrong content | int("abc") (string can't become an integer) |
AttributeError | Calling a method that does not exist on an object | "hello".append("!") (strings have no .append) |
When you see one of these at the bottom of a traceback, you immediately know the category of problem. That narrows your search.
Practice: Read a Second Traceback
Here is a different crash from the SmartNotes module. Read it bottom-up before looking at the explanation.
Traceback (most recent call last):
File "smartnotes_buggy.py", line 93, in average_word_count
return total / len(notes)
~~~~~~^~~~~~~~~~~~
ZeroDivisionError: division by zero
Step 1 -- Bottom line: ZeroDivisionError: division by zero. Something divided by zero.
Step 2 -- Frame above: File smartnotes_buggy.py, line 93, function average_word_count. The expression total / len(notes) crashed.
Step 3 -- Why: len(notes) must be 0, meaning notes is an empty list. The function does not check for that before dividing.
Open smartnotes_buggy.py and look at average_word_count. The docstring says "Returns 0.0 if the list is empty," but the code never checks. The fix: add if not notes: return 0.0 before the division.
PRIMM-AI+ Practice: Predict the Crash
Predict [AI-FREE]
Press Shift+Tab to enter Plan Mode before predicting.
Look at this code. Without running it, predict: which line crashes, and what error type does Python raise? Write your prediction and a confidence score from 1 to 5.
def tag_summary(note_data: dict) -> str:
"""Return a comma-separated string of tags."""
tags: list[str] = note_data["tags"]
title: str = note_data["title"]
count: int = len(tags)
return title + ": " + tags + " (" + str(count) + " tags)"
# This call triggers the crash:
tag_summary({"title": "My Note", "tags": ["python", "ai"]})
Check your prediction
Crash line: return title + ": " + tags + " (" + str(count) + " tags)"
Error type: TypeError
Why: tags is a list, and you cannot concatenate a list with a string using +. Python raises TypeError: can only concatenate str (not "list") to str. The fix: use ", ".join(tags) to convert the list to a string first.
If you predicted TypeError on that line, your traceback instincts are forming.
Run
Press Shift+Tab to exit Plan Mode.
Create a file called traceback_practice.py with the code above. Run uv run python traceback_practice.py. Read the traceback bottom-up and confirm it matches your prediction.
Investigate
In Claude Code, type:
/investigate @traceback_practice.py
Ask why Python cannot concatenate a list with a string and what alternatives exist. Compare the AI's explanation to your own understanding.
Modify
Change the code so it triggers a KeyError instead of a TypeError. (Hint: access a key that does not exist in note_data.) Predict the exact error message before running.
Make [Mastery Gate]
Write three small functions, each designed to crash with a specific error type:
- A function that raises
TypeErrorwhen called - A function that raises
KeyErrorwhen called - A function that raises
ValueErrorwhen called
For each one, predict the full last line of the traceback (error type and message) before running. Run each and compare. All three predictions must match.
Try With AI
If Claude Code is not already running, open your terminal, navigate to your SmartNotes project folder, and type claude. If you need a refresher, Chapter 44 covers the setup.
Prompt 1: Check Your Understanding
I just learned how to read Python tracebacks. Here is my
understanding: "You read a traceback top to bottom. The
first line tells you the error type." Is that accurate?
Read the AI's correction carefully. It should point out that tracebacks read bottom-up, and the error type is on the last line. Compare its explanation to what you learned in this lesson.
What you're learning: You are testing your own mental model against the AI's knowledge and catching your own misconceptions.
Prompt 2: Generate a Crash
Write a SmartNotes function called find_longest_note that
takes a list of Note objects and returns the one with the
highest word_count. Include a bug that causes a crash when
the list is empty. Do NOT include the fix.
Run the generated code with an empty list. Read the traceback bottom-up. Identify the error type, the line, and the function. Then fix it yourself before asking the AI.
What you're learning: You are practicing the bottom-up reading method on a traceback you have never seen before, using AI-generated code as your diagnostic material.
Prompt 3: Classify the Error
/bug @smartnotes_buggy.py
The function reading_time_seconds crashes with a TypeError.
Classify this bug using the Error Taxonomy from Chapter 43.
Compare the AI's classification to what you see in the traceback. The Error Taxonomy categories are: Type Error, Logic Error, Specification Error, Data/Edge-Case Error, Orchestration Error. This bug is a Type Error (wrong data type passed to an operation).
What you're learning: You are connecting traceback reading to the Error Taxonomy, building the habit of classifying bugs by category before attempting a fix.
James leans back. "So the rule is: read the bottom first. The last line tells you what broke. The line above tells you where. Everything above that is just the path Python took to get there."
Emma nods. "Think of it like reading a shipping damage report. When a package arrives crushed at your distribution center, you don't start at the origin warehouse. You start with the damage: what broke. Then you check the last handler: where it broke. Then you trace backwards through the transit chain to find why."
"That's exactly it," James says. "Bottom-up. Damage first, then trace."
"And now you know four error types: TypeError, KeyError, ValueError, AttributeError. When one of those shows up at the bottom of a traceback, you already have a hypothesis before you even look at the code."
James glances at the smartnotes_buggy.py file. "But some bugs don't crash. The reading_time_minutes function returns 0 instead of 0.5 for a short note. No traceback, no red text. How do I find those?"
Emma smiles. "That's the next lesson. When there's no traceback, you need a different tool: print."