You MUST use this whenever something is broken, before proposing or making any fix, including quick fixes and when the user is in a hurry: a bug, a failing or red test, a build or CI failure, a crash, a performance problem, unexpected behavior. Finds the root cause in four phases (investigate, compare, test one hypothesis, fix with a failing test first) instead of guessing, and stops after three failed fixes to question the design. Not for writing new features.
Guessing at fixes feels fast and is slow. Each guess that half-works hides the real cause a little deeper, and the third patch usually breaks something the first one touched. This skill makes you find out why before you change anything.
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
If you have not finished Phase 1, you cannot propose a fix. Not a "quick one to try", not "while we investigate". Suggesting a fix in your reply counts as proposing one.
The process scales. For a simple bug the four phases take a minute: read the error, confirm the cause, write the failing check, fix it. You never skip them, you just move through them quickly.
Any technical problem: test failures, production bugs, unexpected behavior, performance regressions, build or CI failures, integration issues, flaky tests.
Use it especially when:
Before attempting ANY fix:
Read the error completely. The whole message and stack trace, not the first line. Note file paths, line numbers and error codes. Errors often state the exact problem.
Reproduce it. Find the exact steps or command that trigger it, and check whether it happens every time. If you cannot reproduce it, gather more data (logs, instrumentation); do not guess.
Check what changed. git diff, recent commits, new dependencies,
config and environment differences. If there is a known-good commit and
a command that shows the bug, let git find the culprit:
git bisect start <bad> <good> && git bisect run <repro-command>,
then git bisect reset to return to your branch before you change
anything.
Gather evidence across component boundaries. When the system has layers (CI → build → deploy, UI → API → database, CLI → config → child process), do not guess which layer breaks. Add temporary logging at each boundary, run once, and let the output show where good data turns bad:
# Layer 1: CI job
echo "API_TOKEN in job: $([ -n "${API_TOKEN:-}" ] && echo SET || echo UNSET)"
# Layer 2: build script
echo "config file: ${CONFIG_PATH:-<unset>}"; test -r "${CONFIG_PATH:-}" && echo readable
# Layer 3: the service at startup
node -e 'console.log("NODE_ENV=", process.env.NODE_ENV)'
Log whether a secret is set, never its value. Then investigate the one layer the evidence points at.
Trace the data backward. When the error surfaces deep in a call
stack, ask where the bad value came from, then what called that, and
keep going until you reach the source. Fix at the source, not where it
crashed. Full technique: references/root-cause-tracing.md.
Done when you can say what is wrong and why, backed by something you observed, not something you assumed.
test-driven-development skill is installed, follow it here.verification-before-completion skill is installed, follow it
before you claim success.references/defense-in-depth.md covers where to put them).Signs the problem is the design, not a bug:
Do not attempt fix number four. Tell the user what you tried, what each attempt revealed, and why you think the pattern itself is unsound, and decide together whether to refactor or keep patching.
If you catch yourself thinking or writing any of these:
And if the user says things like "Is that actually happening?", "Stop guessing", "Will that show us anything?" or "Are we stuck?", you skipped the evidence. Go back to Phase 1.
| Excuse | Reality | |--------|---------| | "It's simple, I don't need the process" | Simple bugs have root causes too, and the process is fast for them. | | "It's an emergency, no time" | Systematic debugging is faster than guess-and-check thrashing. | | "I'll try this first, then investigate" | The first guess sets the pattern for every attempt after it. | | "I'll add the test after the fix works" | A fix you never saw fail proves nothing. | | "Several fixes at once saves time" | Then you cannot tell which one worked or which one broke something. | | "I can see the problem" | Seeing the symptom is not understanding the cause. |
If the investigation shows the issue really is environmental, timing dependent or external (a flaky network, a third-party outage):
Be suspicious of this conclusion: before accepting it, ask what you have
not checked yet, because an investigation that stopped early looks the
same from the inside. For flaky tests, check for race conditions first;
see references/condition-based-waiting.md.
When you finish, tell the user in a few lines:
references/root-cause-tracing.md: trace a bad value backward through
the call stack to the place it was created.references/defense-in-depth.md: add validation at several layers once
the cause is fixed, so the same bug cannot come back silently.references/condition-based-waiting.md and
references/condition-based-waiting-example.ts: replace arbitrary
sleeps in tests with polling for the real condition.scripts/find-polluter.sh: run test files one by one to find which
test creates an unwanted file or state. Run it from the project root
with the path to this skill's copy, for example
bash .claude/skills/systematic-debugging/scripts/find-polluter.sh packages/core/.git 'src/**/*.test.ts'
(TEST_CMD sets the runner; default npm test).Copy a source-pinned command for your client. You run it yourself.
Destination: .claude/skills/systematic-debugging · pinned to the source commit
# Run from your project root
git clone https://github.com/davila7/claude-code-templates.git .skillboard-tmp
git -C .skillboard-tmp checkout 5cc17cf267132cc690dee82239b80a99ec82e4f8
mkdir -p ".claude/skills"
cp -r ".skillboard-tmp/cli-tool/components/skills/development/systematic-debugging" ".claude/skills/"
rm -rf .skillboard-tmpReview the source before running. This copies files into your project; it is not a one-click install and does not verify runtime safety.
sudo apt update && sudo apt install -y gitnpm install -g @anthropic-ai/claude-code# Run from your project root
git clone https://github.com/davila7/claude-code-templates.git .skillboard-tmp
git -C .skillboard-tmp checkout 5cc17cf267132cc690dee82239b80a99ec82e4f8
mkdir -p ".claude/skills"
cp -r ".skillboard-tmp/cli-tool/components/skills/development/systematic-debugging" ".claude/skills/"
rm -rf .skillboard-tmpDestination: .claude/skills/systematic-debugging
Scanner static-checks@0.1.0 · commit 5cc17cf26713. Static checks cannot prove runtime safety – review the source and the exact diff before installing. How checks work.
References credentials, tokens or secret files that a skill should not need.
Evidence: [redacted]· fingerprint e9cbb0224c4a3d23