---
name: debugging
description: "Find and fix the root cause of a bug instead of patching symptoms — reproduce it, narrow it down, test one hypothesis at a time, fix the cause, and add a test so it can't come back. Use it when something is broken, a test or check fails for a reason you don't understand, an error shows up in the logs, or a fix didn't hold and the bug came back."
license: MIT
metadata:
  author: "Gaitro"
  category: "workflow"
  copyright: "Copyright (c) 2026 Farnor"
  source: "https://gaitro.com/skills/debugging"
---

# Debugging

Guessing and patching is how a one-line bug becomes an afternoon. Work the loop below, one variable at a time, and stop when the evidence — not a hunch — points to the cause.

## Steps

1. **Read the whole error first.** The full message, the stack trace down to its first frame in *your* code, the input or request that triggered it, and the logs from the same moment. Many bugs are explained right there, before any code changes.
2. **Search what's already known.** The exact error text in the codebase, the issue tracker, and the changelog of the dependency involved. In a Gaitro project, run `gaitro knowledge search "<error text>"` — another agent may have solved it already.
3. **Reproduce it reliably.** Find the smallest input or sequence that triggers it every time, and capture it as a command or a failing test. If you can't reproduce it, you can't confirm a fix — gather more evidence (log the failing input, the actual values) before you change anything.
4. **Narrow it down.** Halve the search space each time instead of reading everything: which commit introduced it (`git bisect`), which part of the input triggers it, which layer is wrong (the request, the handler, the query, the stored data).
5. **Test one hypothesis at a time.** State it — "the cache returns a stale row because its key leaves out the tenant id" — predict what you'd observe if it's true, then check with one change, one log line, or one assertion. Undo whatever didn't confirm it.
6. **Fix the cause, not the symptom.** If a value is wrong, find where it *became* wrong, not where it was noticed. A `try/catch` that hides the error, a retry, an extra null check, or a sleep usually moves the bug rather than fixing it.
7. **Prove the fix.** The reproduction from step 3 now passes on the new code and still fails on the old, and the full suite passes.
8. **Stop it coming back.** Keep the reproduction as a regression test named for the behaviour ("an order's total applies the discount once"). In a Gaitro project, keep it as a project check: `gaitro check add "<sentence>" --run "<test command>" --keep`.
9. **Look for siblings.** The same mistake is often made in more than one place — search for the pattern you just fixed.

## When you're stuck

- Check that the code you're reading is the code that's running: the deployed version, build caches, environment variables, feature flags.
- Print the actual value instead of trusting the one you expect.
- Shrink the reproduction further until the cause has nowhere to hide.
- After two hypotheses fail, write down what you know and what you've ruled out before trying a third.

## Done when

- [ ] The bug reproduces reliably on the old code and not on the new
- [ ] The root cause is identified and explained in a sentence or two
- [ ] A regression test fails without the fix and passes with it
- [ ] The full suite passes
- [ ] Temporary debugging code (extra logging, flags, prints) is removed

## Report back

What was wrong, in one sentence a non-developer can follow; the root cause and where it lives (`file:line`); the fix; the test that now guards it; and any similar spots you found.

## Traps

- Changing several things at once, so you can't tell which one fixed it.
- "Fixing" a flaky test with retries or longer waits.
- Swallowing the error so the symptom disappears.
- Calling it fixed without having reproduced it first.
