Debugging
Intro
Debugging is a fixed loop: reproduce the failure, read the error,
isolate the cause, fix it, verify the fix, and add a regression
test. Skipping steps causes the bug to come back.
Overview
The debugging loop
- Reproduce — get the exact steps, inputs, and error
messages. Confirm you can trigger the issue on demand.
- Read the error — parse the full stack trace or error
message. The root cause is usually in the first or last frame,
not the middle.
- Isolate — narrow the scope:
- Binary search through code (comment out halves)
- Check recent changes (
git log, git diff)
- Add targeted logging or print statements
- Test with the minimal failing input
- Hypothesize and test — form a theory, then test it. Change
one thing at a time.
- Fix — apply the minimal change that resolves the issue.
- Verify — confirm the original error is gone, run the full
test suite for regressions, and exercise edge cases near the
fix.
- Document — add a test that would have caught this bug, and
a comment if the fix is non-obvious.
Common diagnosis paths
- Tests pass locally, fail in CI — environment differences:
OS, dependency versions, file paths, timezone, locale, or race
conditions in parallel tests. Compare CI logs with local output
to find the divergence point.
- Function returns wrong results for some inputs — get the
specific failing inputs, add assertions at intermediate steps,
find where actual diverges from expected, trace back.
Gotchas
Agent-specific failure modes — provider-neutral pause-and-self-check items:
- Changing multiple things at once. When more than one variable changes between a broken and fixed state, you cannot know which change fixed the issue — and you may have introduced new bugs alongside the fix. Change one thing at a time.
- Skipping the reproduction step. Starting to diagnose before having a reliable reproduction means you're hunting in the dark. Confirm you can trigger the failure on demand before doing anything else.
- Stopping at the first fix that makes the symptom disappear. The first change that silences the error may be masking the root cause. Verify that the underlying state is correct, not just the visible output.
- No regression test after the fix. A bug that was fixed without a test will return. Add a test that would have caught it before closing the issue.
- Adding print statements everywhere without a plan. Each print should test a specific hypothesis ("is the value non-nil here?"). Random prints produce noise that slows diagnosis.
- Confusing correlation with causation in logs. The last log entry before a crash is rarely the cause — it is just the last thing that ran. Trace the full causal chain; do not assume temporal proximity implies causation.
- Treating a CI/local mismatch as mysterious. Environment differences (OS, timezone, locale, implicit tool version, parallel test ordering) are the most common cause. Diff the CI environment against local systematically rather than guessing.
Full reference
Anti-patterns
- Shotgun debugging — changing many things at once. You won't
know which fix worked, and you'll introduce new bugs.
- Print-statement spelunking with no plan — adding prints is
fine, but each one should test a specific hypothesis.
- Skipping reproduction — "it sometimes fails" is not a bug
report. Find the trigger first.
- Stopping at the first fix — the first thing that makes the
symptom go away is often masking the real cause. Verify the
underlying state is correct, not just the visible output.
- No regression test — if you don't add a test, the bug will
return.
Tools and techniques
git bisect for regressions: a known-good and known-bad
commit narrows the offending change in log(N) steps.
- Debuggers over prints when state is rich (object graphs,
step-through control flow).
- Differential debugging — diff the working case against the
failing case, not the other way around.
- Rubber-duck explanation — articulating the problem out loud
often surfaces the bug before you write any code.
When to stop and ask
If you've spent more than ~30 minutes without forming a viable
hypothesis, stop and either gather more information (logs,
reproduction steps, recent commits) or escalate. Continued flailing
is not progress.
1---2name: debugging3description: Systematic debugging — reproduce, read the error, isolate, hypothesize, fix, verify, document. Use when tracking down a bug, unexpected behavior, or test failure — including phrases like "this doesn't work", "why is this failing", or "help me debug".4---56# Debugging78## Intro910Debugging is a fixed loop: reproduce the failure, read the error,11isolate the cause, fix it, verify the fix, and add a regression12test. Skipping steps causes the bug to come back.1314## Overview1516### The debugging loop17181. **Reproduce** — get the exact steps, inputs, and error19 messages. Confirm you can trigger the issue on demand.202. **Read the error** — parse the full stack trace or error21 message. The root cause is usually in the first or last frame,22 not the middle.233. **Isolate** — narrow the scope:24 - Binary search through code (comment out halves)25 - Check recent changes (`git log`, `git diff`)26 - Add targeted logging or print statements27 - Test with the minimal failing input284. **Hypothesize and test** — form a theory, then test it. Change29 one thing at a time.305. **Fix** — apply the minimal change that resolves the issue.316. **Verify** — confirm the original error is gone, run the full32 test suite for regressions, and exercise edge cases near the33 fix.347. **Document** — add a test that would have caught this bug, and35 a comment if the fix is non-obvious.3637### Common diagnosis paths3839- **Tests pass locally, fail in CI** — environment differences:40 OS, dependency versions, file paths, timezone, locale, or race41 conditions in parallel tests. Compare CI logs with local output42 to find the divergence point.43- **Function returns wrong results for some inputs** — get the44 specific failing inputs, add assertions at intermediate steps,45 find where actual diverges from expected, trace back.4647## Gotchas4849Agent-specific failure modes — provider-neutral pause-and-self-check items:5051- **Changing multiple things at once.** When more than one variable changes between a broken and fixed state, you cannot know which change fixed the issue — and you may have introduced new bugs alongside the fix. Change one thing at a time.52- **Skipping the reproduction step.** Starting to diagnose before having a reliable reproduction means you're hunting in the dark. Confirm you can trigger the failure on demand before doing anything else.53- **Stopping at the first fix that makes the symptom disappear.** The first change that silences the error may be masking the root cause. Verify that the underlying state is correct, not just the visible output.54- **No regression test after the fix.** A bug that was fixed without a test will return. Add a test that would have caught it before closing the issue.55- **Adding print statements everywhere without a plan.** Each print should test a specific hypothesis ("is the value non-nil here?"). Random prints produce noise that slows diagnosis.56- **Confusing correlation with causation in logs.** The last log entry before a crash is rarely the cause — it is just the last thing that ran. Trace the full causal chain; do not assume temporal proximity implies causation.57- **Treating a CI/local mismatch as mysterious.** Environment differences (OS, timezone, locale, implicit tool version, parallel test ordering) are the most common cause. Diff the CI environment against local systematically rather than guessing.5859## Full reference6061### Anti-patterns6263- **Shotgun debugging** — changing many things at once. You won't64 know which fix worked, and you'll introduce new bugs.65- **Print-statement spelunking with no plan** — adding prints is66 fine, but each one should test a specific hypothesis.67- **Skipping reproduction** — "it sometimes fails" is not a bug68 report. Find the trigger first.69- **Stopping at the first fix** — the first thing that makes the70 symptom go away is often masking the real cause. Verify the71 underlying state is correct, not just the visible output.72- **No regression test** — if you don't add a test, the bug will73 return.7475### Tools and techniques7677- **`git bisect`** for regressions: a known-good and known-bad78 commit narrows the offending change in log(N) steps.79- **Debuggers over prints** when state is rich (object graphs,80 step-through control flow).81- **Differential debugging** — diff the working case against the82 failing case, not the other way around.83- **Rubber-duck explanation** — articulating the problem out loud84 often surfaces the bug before you write any code.8586### When to stop and ask8788If you've spent more than ~30 minutes without forming a viable89hypothesis, stop and either gather more information (logs,90reproduction steps, recent commits) or escalate. Continued flailing91is not progress.