ChatGPT Prompts for Debugging Python Code, Matched to the Symptom

Programmer working on a laptop while debugging code

A good ChatGPT or Claude prompt for debugging Python code gives the model three things it cannot guess: the full traceback (or, for a bug with no crash, one concrete input with the expected and the actual output), the code for the failing function and the functions it calls, and the exact Python and library versions. Then it asks for a diagnosis before a fix. Prompts that say only “fix my code” invite the model to pattern-match on the most common cause of that error, which is often not yours.

Below are six prompts for the most common debugging situations, a guide to which one to use, a worked example of a silent bug, and the mistakes that make AI debugging slower than doing it by hand.

Pick the prompt by symptom

Debugging prompts work best when they match the kind of failure. A crash with a traceback, a wrong result with no error, and a failure that only happens sometimes need different evidence and different reasoning.

SymptomWhat to pastePrompt to use
Crash with a traceback you do not understandFull traceback onlyPrompt 1: explain first
Crash, and you want the cause and fixTraceback, failing function, its direct calleesPrompt 2: traceback analysis
Runs fine, wrong outputOne input, expected output, actual output, functionPrompt 3: divergence trace
Fails sometimesSymptom, frequency, environment, codePrompt 4: non-determinism audit
Test fails intermittently in CITest, code under test, failure logPrompt 5: flaky test
You have a fix and want to check itOriginal code, bug, proposed fixPrompt 6: fix verification

Prompt 1: Explain an unfamiliar traceback before fixing anything

When the error is in a library you do not know, or the traceback is long, understanding it first prevents you from applying a fix you cannot evaluate. A traceback is Python’s report of the chain of function calls that were active when an exception was raised, most recent call last.

Explain this Python traceback in plain English. Do not suggest a fix
yet; I want to understand what happened first.

1. Start from the last line: what exception is this and what does it
   usually mean?
2. Walk up the frames and say, for each one that is in MY code (not
   site-packages or the standard library), what that frame was doing.
3. If the traceback contains "During handling of the above exception"
   or "The above exception was the direct cause", explain which
   exception is the original problem.
4. Tell me which single frame is the best place to start reading.

Python version: <e.g. 3.12.4>

<traceback>
PASTE THE FULL TRACEBACK
</traceback>

Point 3 matters more than it looks. When one exception is raised while another is being handled, Python prints both, separated by a line like “During handling of the above exception, another exception occurred”, as shown in the traceback module documentation. The last error printed is not always the original cause, and models sometimes fix the second exception while ignoring the first.

Include the Python version. Since Python 3.11, tracebacks underline the exact expression that failed within a line, a change introduced by PEP 657. Those carets are useful evidence; paste them as-is and do not trim the traceback to “just the error line”.

Prompt 2: Full traceback analysis with a minimal fix

Once you know roughly what happened, this prompt asks for the root cause and the smallest change that fixes it.

Here is a traceback and the code involved.

1. Walk through the call stack from the outermost frame to the
   innermost, one line per frame.
2. Identify the exact line and the condition that raised the exception
   (which variable had which value or type).
3. Say whether that line is the root cause or only where the problem
   surfaced. If the bad value came from earlier, trace it back.
4. Propose the minimal fix at the root cause, as a diff.
5. List anything you had to assume because it is not in what I pasted.

<traceback>
PASTE TRACEBACK
</traceback>

<code>
PASTE THE FAILING FUNCTION AND THE FUNCTIONS IT CALLS DIRECTLY
</code>

Environment: Python <version>, <library>==<version>

Two lines do most of the work. Step 3 separates where an error surfaced from where it started; a KeyError in a template renderer is usually a data problem upstream, not a rendering bug. Step 5 makes the model’s guesses visible. If it lists “assumed config is always a dict”, you know to check that before trusting the fix.

Paste the failing function and its direct callees, not the whole file. More code is not more context if most of it is irrelevant; it gives the model more places to wander off.

Prompt 3: Find where a silent bug first goes wrong

Silent bugs, where code runs without an exception but produces a wrong result, are where vague prompts fail worst, because there is no error message to anchor the model. The fix is to give it one concrete case and ask for a trace.

This function runs without errors but returns the wrong result.

Input: <exact input value>
Expected output: <value> because <one-line reason from the spec>
Actual output: <value>

Trace the execution for this input step by step, showing the value of
each relevant variable after each statement. Tell me at which step the
value first diverges from what it should be, and why. Only then
suggest a fix.

<code>
PASTE FUNCTION
</code>

The “because” in the expected output is easy to skip and should not be. It tells the model what rule the code is supposed to follow, which is the thing it needs to spot the divergence.

Worked example: a silent bug in a discount function

The following example is illustrative. A function applies percentage discounts to an order total and adds a 5% loyalty discount for loyalty members. Support reports that some customers get more discount than they should, and that the amount seems to grow during the day.

def apply_discounts(total, discounts=[], loyalty=False):
    if loyalty:
        discounts.append(5)
    for pct in discounts:
        total = total - total * pct / 100
    return round(total, 2)

A vague prompt (“customers get the wrong price, fix it”) often gets a reply about floating-point rounding, because that is a common cause of wrong prices. That is a plausible guess and the wrong one here.

With Prompt 3 the developer supplies a concrete sequence:

Input: three calls in the same process:
  apply_discounts(100, loyalty=True)
  apply_discounts(100, loyalty=True)
  apply_discounts(100)
Expected output: 95.0, 95.0, 100.0 because loyalty members get one 5%
discount per order and non-members get none
Actual output: 95.0, 90.25, 90.25

A step-by-step trace of the second call shows discounts already containing [5] before the function body runs, which is the divergence point. The cause is the default argument discounts=[]: Python evaluates a default once, when the function is defined, so every call that relies on it shares and keeps mutating the same list. The third call is the giveaway; a non-member gets the discounts left behind by earlier calls. The fix is to default to None and build a fresh list inside the function, which also stops it from mutating a list the caller passed in:

def apply_discounts(total, discounts=None, loyalty=False):
    applied = list(discounts or [])
    if loyalty:
        applied.append(5)
    for pct in applied:
        total = total - total * pct / 100
    return round(total, 2)

With this version the same three calls return 95.0, 95.0 and 100. The point of the example is the process: a concrete sequence of inputs with expected values found a shared-state bug that a guess about rounding would have missed, and the same sequence becomes the regression test.

Prompt 4: Audit an intermittent bug for non-determinism

When a bug appears only sometimes, the model cannot reproduce it, so ask it to reason about what could vary between runs.

This bug happens intermittently: <symptom>, roughly <frequency>,
seen in <environment: local, CI, production, under load?>.

List every source of non-determinism in the code below, for example:
threads or async tasks sharing state, iteration over sets or unordered
data, time and time zones, random numbers without a fixed seed,
mutable default arguments or module-level state, network or database
timing, environment variables.

Rank them by how likely each is to cause THIS specific symptom, and for
the top three tell me how to make the bug reproduce reliably.

<code>
PASTE CODE
</code>

The ranking instruction is what turns a generic list into something actionable. The reproduction request is the real goal: an intermittent bug you can trigger on demand is most of the way to fixed.

Prompt 5: Root-cause a flaky test

A flaky test is one that passes and fails on the same code. In Python test suites the usual suspects are shared state between tests, real clocks or randomness, test order dependence, and async code that is not properly awaited.

This test passes locally and fails about <N> times in <M> CI runs.

Check the test and the code it exercises for:
1. State shared between tests (module globals, class attributes,
   fixtures with too wide a scope, files or databases not cleaned up)
2. Dependence on test execution order
3. Real time, dates, time zones or sleeps
4. Randomness without a fixed seed
5. Async code that is not awaited or awaited in the wrong place
6. External services or network calls

For each issue you find, show the line and the specific fix.

<test>
PASTE TEST
</test>
<code_under_test>
PASTE CODE
</code_under_test>
<failure_log>
PASTE CI OUTPUT
</failure_log>

If the fix involves rewriting the test, the unit test prompts cover how to get deterministic, well-isolated tests from the start.

Prompt 6: Verify the fix before applying it

Models produce fixes that resolve the reported case and quietly change behavior for others. One more prompt catches most of those.

Here is the original function, the bug, and a proposed fix.

1. Check whether the fix changes behavior for any input that
   previously worked correctly. Give a concrete input if it does.
2. List edge cases the fix does not handle.
3. Write two or three regression tests that fail on the original code
   and pass on the fixed code.

The regression tests are the lasting output. Commit them with the fix so the bug cannot come back silently.

When to stop prompting and open a debugger

AI debugging is reasoning about code it cannot run. When two rounds of prompting do not converge, get real evidence from the running program and paste that instead. Python’s built-in debugger, pdb, is enough for most cases:

  • Put breakpoint() on the line before the suspicious one, run the code, and use p variable to print values, n to step over a line and s to step into a call.
  • Run a script as python -m pdb script.py; if it crashes, pdb drops into post-mortem mode at the failing frame so you can inspect the state that caused it.
  • In an interactive session, import pdb; pdb.pm() after an exception does the same.

Paste the values you see back into Prompt 2 or 3. Real variable values beat any amount of inference.

Mistakes that slow AI debugging down

  • Trimming the traceback. The frames you think are irrelevant are often where the bad value came from.
  • Leaving out versions. Library behavior changes between releases, and models often suggest APIs from older ones. The reasons are covered in why AI coding assistants suggest deprecated APIs.
  • Accepting the first fix without a diagnosis. If you cannot say why the bug happened, you cannot tell whether the fix is right.
  • Pasting secrets and production data. Tracebacks and logs can contain tokens, connection strings and personal data. Redact before pasting into any hosted model.
  • Long conversations that drift. After several failed attempts, start a fresh chat with a clean summary of what you know; the advice in keeping long AI conversations on track applies directly.

The same structure works in other languages; only the traceback format changes. For browser and Node.js errors, see the prompts for debugging JavaScript and TypeScript errors.

Sources

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top