Prompting for Debugging and Error Analysis
What Makes Debugging Prompts Effective When code fails, it is tempting to paste the error message and ask "How do I fix this?" Sometimes that works. Often, it produces a confident fix for the wrong problem, because the model has to guess the code, the inputs, the environment, and what you expected.
Effective debugging with a model follows the same discipline as debugging without one:
- Collect evidence: error, code, input, expected vs actual, environment
- Form hypotheses: possible causes, ranked by likelihood
- Test hypotheses: add checks or logs, reproduce, narrow down
- Fix the cause: smallest change that addresses the root cause
- Prove the fix: a test that failed before and passes after This lesson provides prompts for each stage and a small tool that turns a long traceback into the compact evidence a model needs. Debugging multi-step prompt chains is covered separately in Lesson 74.
Stage 1: Collect the Evidence What to Include Evidence Why It Matters Full error message and traceback Shows the exception type, message, and where it happened The relevant code The model cannot fix code it cannot see The input that triggered it Many bugs occur only for specific inputs Expected vs actual behavior Defines what "fixed" means, especially for bugs without errors Environment Language, framework, and library versions What you already tried Avoids repeated suggestions (Lesson 6) Recent changes Bugs often appear right after a change Summarizing a Long Traceback A traceback lists the chain of function calls that led to an error. Framework tracebacks are often long, and most frames are inside the framework, not your code. The frames in your code are usually where the problem is. A small parser can extract them:
import json import re
def summarize_traceback(traceback_text: str, project_marker: str = "myapp") -> dict: """Extract the error line and the frames that belong to the project.""" error_line = traceback_text.strip().splitlines()[-1] frames = re.findall(r'File "([^"]+)", line (\d+), in (\w+)\n\s+(.+)', traceback_text) project_frames = [ f"{path}:{line} in {function}: {code.strip()}" for path, line, function, code in frames if project_marker in path ] return {"error": error_line, "project_frames": project_frames, "total_frames": len(frames)}
traceback_text = '''Traceback (most recent call last): File "/usr/lib/python3.12/site-packages/django/core/handlers/exception.py", line 55, in inner response = get_response(request) File "/home/dev/myapp/courses/views.py", line 42, in enroll total = course_total(course.fee, discount, gst) File "/home/dev/myapp/courses/pricing.py", line 12, in course_total return round(fee (100 - discount) / 100 (100 + gst) / 100) TypeError: unsupported operand type(s) for -: 'int' and 'str' '''
print(json.dumps(summarize_traceback(traceback_text), indent=2)) Output:
{ "error": "TypeError: unsupported operand type(s) for -: 'int' and 'str'", "project_frames": [ "/home/dev/myapp/courses/views.py:42 in enroll: total = course_total(course.fee, discount, gst)", "/home/dev/myapp/courses/pricing.py:12 in course_total: return round(fee (100 - discount) / 100 (100 + gst) / 100)" ], "total_frames": 3 } Code Explanation The error line is the last line of a Python traceback: the exception type and message. The regular expression captures each frame's file path, line number, function name, and the line of code shown below it. project_marker filters frames whose path contains your project folder name. Framework frames are excluded, but still counted in total_frames. The summary points directly to the problem: discount is a string where a number is expected. Line 42 in views.py passes it to course_total. Because the value probably comes from a form or URL parameter, the fix likely belongs in the view, which should convert and validate the input, not in the pricing function.
Note: Remove secrets, personal data, and internal hostnames from tracebacks and logs before sending them to a model.
Stage 2: Form Ranked Hypotheses Prompt Help me debug this problem. Do not write a fix yet.
Environment: {environment} Expected behavior: {expected} Actual behavior: {actual} Input that triggers it: {input} What I already tried: {tried}
{error_summary}
{relevant_code_with_line_numbers}
List the 3 most likely causes, ranked. For each:
- Why the evidence supports it
- What evidence would confirm or rule it out
- The quickest check to perform Why It Works "Do not write a fix yet" prevents the most common debugging mistake: applying a guessed fix before understanding the cause. Ranked hypotheses keep alternatives visible. The first guess is not always right. A check per hypothesis turns ideas into actions that produce evidence. Example Hypotheses for the Traceback Above
- The discount value comes from request data (a string) and is passed without conversion. Check: print type(discount) in the view.
- The Course model stores the discount as a CharField. Check: the model definition.
- A default value is set as a string somewhere, such as discount = "0". Check: search for where discount is assigned. Stage 3: Test Hypotheses Prompt for Diagnostic Checks For the hypothesis "{hypothesis}", give me the smallest diagnostic step to confirm or rule it out: a log statement, an assertion, or a command to run. Show exactly where to put it in
, and what output would confirm the hypothesis. Prompt for a Minimal Reproduction A minimal reproduction is the smallest piece of code that shows the bug, without the rest of the application. It makes the cause obvious and is ideal for tests and bug reports.
Create a minimal, self-contained Python script that reproduces the error in , using only the logic from that is needed. Use fixed sample values instead of database or request data. Narrowing Down With Differences When something "used to work", compare the working and broken states:
This code worked before the change in and fails after it with . Which part of the change is most likely responsible, and why? Tools such as git bisect can find the commit that introduced a bug automatically, by testing commits in a binary search.
Stage 4: Fix the Root Cause Prompt The cause is confirmed: {confirmed_cause}
Propose the smallest fix that addresses this root cause.
- Change only what is necessary.
- Fix the cause, not the symptom (for example, validate input where it enters the system rather than adding try/except where it fails).
- Explain why the fix works, and whether any other code could have the same problem.
{relevant_code} Symptom vs Root-Cause Fixes Problem Symptom Fix Root-Cause Fix discount arrives as a string int(discount) inside course_total Validate and convert discount in the view or form where request data enters A query sometimes returns no row Wrap in try/except and return a default Find why the row is missing, or handle the "not found" case explicitly and correctly A loop skips the last item Add a special case for the last item Fix the loop's range Symptom fixes make the error disappear but often hide the real problem, which reappears elsewhere.
Avoid Silencing Errors A common model suggestion is to wrap failing code in a broad try/except that ignores errors. Unless failure is truly expected and handled meaningfully, this hides bugs and makes future debugging harder. Ask explicitly: "Do not suppress exceptions unless the error is expected and handled."
Stage 5: Prove the Fix Prompt Write a pytest test that reproduces the bug described in : it must fail with the current code and pass with the fix. Use the minimal reproduction values. Why This Matters A test that fails before the fix proves it actually reproduces the bug. A test that passes after proves the fix works. The test stays in the test suite as a regression test, so the bug cannot return unnoticed (Lessons 74 and 118). Debugging Bugs Without Errors Some of the hardest bugs produce no error: wrong totals, missing records, or wrong display. For these, the expected vs actual description is essential.
Prompt This function returns the wrong result, without any error.
Input: {input} Expected output: {expected} Actual output: {actual}
{numbered_code}
Trace the execution for this input step by step, showing variable values, and identify the first line where the actual values differ from what is needed for the expected output. This uses the execution trace from Lesson 92. The "first line where values differ" is the same principle as finding the first failing step in a chain (Lesson 74).
Analyzing Logs Prompt Analyze the application logs in from the period when users reported {problem}.
- Group related entries and identify the main error patterns.
- For each pattern: how often it occurs, when it started, and which request paths or users are affected.
- Identify the earliest entry that suggests the cause.
- List what additional information would help confirm the cause.
Do not assume a cause the logs do not support. Practical Advice Filter logs in code first, by time range, severity, or request ID, so the prompt contains only relevant entries. Count patterns in code for exact frequencies (Lesson 72). Let the model interpret patterns rather than count them. Mask personal data and secrets before sending logs. Common Mistakes Mistake 1: Pasting Only the Error Message What it is: "Fix TypeError: unsupported operand type(s)."
Why it happens: The error seems to describe the problem.
What it causes: Generic advice that may not apply to your code.
How to fix it: Include code, input, expected and actual behavior, environment, and the traceback.
Mistake 2: Fixing Before Understanding What it is: Applying the first suggested fix without confirming the cause.
Why it happens: It is the fastest path to a green screen.
What it causes: Symptom fixes and new bugs.
How to fix it: Ask for ranked hypotheses and checks before fixes.
Mistake 3: Silencing Errors What it is: Accepting fixes that catch and ignore exceptions.
Why it happens: The error disappears.
What it causes: Hidden bugs and corrupted data later.
How to fix it: Fix the root cause; catch only expected errors and handle them meaningfully.
Mistake 4: No Regression Test What it is: Fixing a bug without adding a test.
Why it happens: The fix seems obviously correct.
What it causes: The same bug returns after later changes.
How to fix it: Add a test that fails before the fix and passes after.
Mistake 5: Sending Secrets in Logs or Tracebacks What it is: Pasting raw logs that contain tokens, passwords, or personal data.
Why it happens: Debugging is urgent.
What it causes: Exposure of sensitive information.
How to fix it: Mask secrets and personal data before sending anything.
Best Practices Collect complete evidence. Error, code, input, expected vs actual, environment, attempts, and recent changes.
Summarize long tracebacks. Focus on frames in your own code.
Separate diagnosis from fixing. Ranked hypotheses, then checks, then fixes.
Use minimal reproductions. They make causes obvious and become tests.
Fix root causes, not symptoms. Validate input where it enters the system.
Prove every fix with a test. Failing before, passing after, kept as a regression test.
Protect sensitive data. Mask it before it leaves your environment.
Try It Yourself Exercise 1: Summarize a Real Traceback Trigger an error in one of your Django or Python projects, copy the full traceback, and run summarize_traceback() with your project folder name as the marker. Use the summary in the hypothesis prompt and compare the ranked causes with the actual cause.
Exercise 2: Bug Without an Error Write a function that calculates a course total but applies GST before the discount instead of after. Give the model the input, expected output, actual output, and code, and ask for a trace that finds the first wrong line. Then write a regression test.
Exercise 3: Symptom vs Root Cause For each scenario, write a symptom fix and a root-cause fix: a form field that sometimes arrives empty, a date string in two different formats, and a list that is sometimes None instead of empty. Discuss which fix a model suggested first when you asked for "a fix".