| name | debugging |
| description | Use for bug-fixing and failure-investigation tasks: reproduce failing tests or builds, trace dependency or configuration errors, patch minimal code, audit vulnerabilities, fix package/build breakage, and verify the repaired behavior with regression tests. |
Debugging Investigation Playbook
A debugging task starts from something broken: a failing test, a build error,
a regression, a security finding. The output is almost always a minimal
patch plus proof that the failure is gone. Move through five phases in
order — skipping a phase is the single most common cause of fix-and-break-
again cycles.
Phase 1 — Reproduce
Reproduce the failure exactly. If you cannot reproduce, you cannot debug.
| Capture | Why |
|---|
| The exact command (env vars, working dir, args) | A different invocation hides the bug |
| stdout + stderr verbatim | Test framework summaries strip real errors |
| Exit code | Some failures look like noise on stdout but exit 0 |
| Versions: language runtime, key deps, OS, kernel if relevant | Bug-by-version is common |
Working tree state (git rev-parse HEAD, dirty files) | Reproducibility evidence |
If reproduction depends on environment (a specific image, a cleared cache,
a particular install order), document the pre-conditions explicitly. A
reproduction that "works on my machine" is not yet a reproduction.
Phase 2 — Minimize
Reduce the failing case until removing one more thing makes it pass. The
smaller the fixture, the more obvious the cause.
- Tests: run only the failing one (
pytest path::test_name), then strip fixtures and parametrizations until the failure stays.
- Inputs: bisect on data — half the rows, half the file, one record.
- Codepath: comment out branches, mock callees, narrow to the function whose change actually triggers the failure.
- Versions: bisect dep versions if the failure correlates with an upgrade.
A two-line failing case beats a 200-line one for both diagnosis and
regression-test value.
Phase 3 — Hypothesize
Form hypotheses from the evidence, ranked by what explains the most data.
| Signal | Suggests |
|---|
| Stack trace top frame in your code | Local logic bug |
| Stack trace top frame in a dep | Wrong API contract or dep version |
ImportError, ModuleNotFoundError | Packaging, env markers, missing extra |
| Compiler / linker error | Native build deps, ABI mismatch |
| Assertion failure with values printed | Diff visible vs expected — chase the diff |
| Flaky / order-dependent | Shared state, test isolation, timing |
| New symptom after dep upgrade | Read upstream changelog around the bumped version |
Write the hypothesis down before testing it. A bullet of "I think X because
Y" forces precision and surfaces the next experiment.
Phase 4 — Patch the root cause
Patch the thing that, if reverted, brings the bug back. Symptom-patches
silently regress.
- Patch as small as the evidence permits. Drive-by refactors hide intent and break review.
- For audit-only tasks (CVE triage, dependency reports), the deliverable is the report; do not edit code.
- Add a regression test when a test surface exists. The test that catches this exact bug on a fresh checkout is the receipt that the fix was needed.
- Preserve API contracts. Changing public behavior to make a test pass is rarely the right fix.
Subtypes
- Build / packaging failures. Read
pyproject.toml, setup.cfg, setup.py, lockfiles, compiler logs. Separate build-time from runtime deps. Verify importability after install (python -c "import package"), not just that pip succeeded.
- Dependency / CVE audits. Match package name, version, ecosystem (PyPI vs npm vs Maven), affected version range, fixed version. Reject false positives from same-name-different-ecosystem and from version ranges that do not include the installed version. Record severity per the upstream advisory, not your inference.
- Patch repair (SWE-bench-style). Read the failing test first, then the code under test. The test pins the contract; the fix is the smallest code change that makes the test pass without breaking adjacent tests.
- Config-change traces. Search the repo for the configuration name; build a graph of references; diff before/after; report the affected files with evidence.
Phase 5 — Verify
Confirm the fix and check for collateral damage.
- Run the original reproduction. Exit 0 on what was failing.
- Run adjacent tests (same module, same suite). No new failures.
- For dep/CVE fixes, re-run the scanner or version check; confirm no remaining advisories at the gated severity.
- For build fixes, install or build from a clean working tree, not the dirty one used during patching.
- Stale artifacts pass tests deceptively — clear caches (
__pycache__, .pytest_cache, build dirs, lockfiles when explicitly required) and re-run.
If a full test suite is impractical (slow, requires services), explain what
you did run and the residual risk.
Pitfalls
- "Fixed" while the original failing command still fails — phase 5 caught nothing because phase 1 reproduction was incomplete.
- Symptom patch (try/except around the failing call) — the bug is now silent in production.
- Broad refactor entangled with the fix — review cannot tell which change was load-bearing.
- Trusting scanner output as truth without checking applicability — false positives.
- Local tests pass because a stale artifact, cache, or environment carryover masks the change.
- Test asserts on a printed string that is not part of the contract — fix breaks the test rather than the test catching a real bug.