| name | fable-debugging |
| description | Fable 5's debugging discipline: reproduce first, read reality not memory, one falsifiable hypothesis at a time, explain the mechanism before shipping the fix. Load when chasing any bug, test failure, or behavior you can't explain — especially after your first fix attempt didn't work. |
Fable debugging
Debugging is epistemics under time pressure. Every rule here exists
because the natural failure mode — guess, patch, rerun, repeat — feels
like progress while destroying information.
Reproduce before you theorize. A bug you can't trigger on demand is a
rumor, and a fix for a rumor is a superstition. Getting a reliable
reproduction IS the first task; everything before that is setup. If it
only reproduces sometimes, the conditions of "sometimes" are your first
clue, not an excuse to proceed without one.
Read the whole error. The answer is disproportionately in the part
people scroll past — the second exception in the chain, the line number
of the caller, the "caused by," the warning three lines up. Read every
line of the actual output before forming any theory. Truncated-error
debugging is guessing with extra steps.
Reality beats memory, every time. When behavior contradicts your
expectation, the next read is the library's actual source, the actual
bytes on disk, the actual response body, the actual env var — never your
recollection of how it "should" work. You are debugging precisely
because your model diverged from reality; consulting the model again is
asking the suspect to alibi himself.
One falsifiable hypothesis, one variable per run. State what you
believe ("the config isn't being read") and design the probe whose
outcome can prove you WRONG, not just consistent-with. Change one thing
per run — two simultaneous changes produce zero information regardless
of the result. Log the intermediate value; don't infer it.
Two failed fixes on one theory means the theory is dead. Do not try
variant three. Your model of the mechanism is wrong somewhere upstream —
go widen: re-read the reproduction, question an assumption you marked
"obviously fine," check the environment class below.
Check the boring liars first. A huge share of "impossible" bugs are
environment lies: stale cache, wrong file edited (check the path!), old
process still running, wrong branch, wrong database, unsaved buffer,
dependency version mismatch. Thirty seconds ruling these out beats two
hours of brilliant reasoning about code that isn't even executing.
Shrink the world. Cut the reproduction down — one function, one
request, one row — until the bug either persists in a context small
enough to fully understand, or vanishes, in which case the cause is in
what you removed; binary-search it back in.
Keep a written ledger of ruled-out causes. Past three hypotheses,
memory fails and you re-test candidates you already cleared. One line
each: what you tested, what you saw, what it eliminates. Loops in
debugging are almost always ledger failures.
No mechanism, no fix. When the bug stops reproducing, you're not done
until you can state WHY it happened — which line, which input, which
interaction. A fix without a mechanism is a coincidence you're about to
redeploy; half the time you perturbed timing or state and the bug is in
the mail. Then: make the fix's test fail without the fix, pass with it.