| name | feedback-loop-debugging |
| description | Build a pass/fail signal BEFORE attempting fixes. Use when debugging, diagnosing failures, tests failing, errors recurring, or when a fix didn't work. Trigger: debug, failing test, broken, not working, TypeError, error output, diagnose. |
| metadata | {"type":"protocol","invocation":"both","practice":null} |
Feedback Loop Debugging
This is the skill. A tight feedback loop that goes red on the bug is 90% of debugging. Everything else is mechanical.
The Gate (non-negotiable)
You MUST have a running, failing command before you touch source code.
If you catch yourself reading code to build a theory before a failing command exists — STOP. That impulse is the exact failure this skill prevents. No failing command, no fix attempt.
Phase 1: Build the Loop (spend disproportionate effort here)
Try each until one works:
- Existing failing test —
npx vitest run path/to/test — already have it? Run it now.
- Targeted new test — write one that exercises exactly the broken behavior.
- CLI one-liner —
node -e "require('./src').fn(badInput)" reproducing the symptom.
- Script harness — 5-line script: setup → call → assert → exit 0/1.
- Bisection —
git bisect run <test-command> when you know it worked before.
Be aggressive. Be creative. Refuse to give up on getting a loop.
Phase 1 is DONE when:
All four must be true. If any is missing, you're still in Phase 1.
Tighten the Loop (once you have ANY loop)
A loop that takes 30 seconds is barely useful. Target: < 5 seconds, deterministic, sharp assertion.
Continuous signals (memory leaks, latency, throughput): the loop is measure → ONE change → re-measure. Establish the baseline rate first, state a numeric PASS/FAIL criterion up front ("growth < 5MB over 1000 requests"), and compress time with load (don't wait an hour — hammer it for 60s). Isolate differentially: run with/without a suspect component and compare rates.
For non-deterministic bugs (flaky tests, race conditions, environment-dependent): read references/tighten.md — it covers raising reproduction rate, pinning randomness, and making flakes debuggable.
Phase 2: Red → Green Loop
1. Run loop → FAIL (confirms bug exists)
2. Form ONE hypothesis; predict the outcome BEFORE running
3. Make ONE change (single variable)
4. Run loop → check result
- PASS → verify full test suite, done
- FAIL → revert, try different hypothesis
- Prediction wrong → your model of the bug is wrong; re-read the failure, don't just re-guess
One change at a time. If you change two things and it passes, you don't know which fixed it.
Escalation (hard caps)
- Same approach fails 2x → STOP, change strategy entirely
- 3 different strategies fail → ask the user
- Never retry the same failing approach a third time
For recurring failures where the immediate cause isn't the real cause, read references/five-whys.md — iterative root cause analysis.
Phase 3: Verify
- Original symptom no longer reproduces
- Full test suite passes (not just your loop)
- No debug artifacts left behind
Cannot Build a Loop?
If you cannot reproduce locally or after 3 genuine attempts: STOP. Do not hypothesize. Do not suggest fixes. State:
- What you tried (specific commands)
- Why each didn't produce a signal
- What you need from the user (access, repro steps, environment, logs, artifacts)
"Can't reproduce" is not a starting point for theorizing — it's a STOP signal. The next step is gathering artifacts, not guessing.
Anti-Patterns (immediate score 1 in any review)
- Reading code and guessing at a fix without reproducing
- Saying "I think the issue is..." without a failing command
- Running full test suite as your loop (too slow, too noisy)
- Modifying code then writing a passing test (proves nothing)
- Changing multiple things between loop runs