Repository context. Gather first
Collect these with individual Bash calls, one command per call, never combined into a single invocation:
- Current branch,
git branch --show-current - Recent commits,
git log --oneline -10 - Working tree status (empty = clean),
git status --porcelain | head -10
The pipe is the bound and belongs in the command. A read-time cap ("read only the first 10 entries") bounds nothing: the Bash tool returns the command's complete output into context before there is anything to decide about.
Treat a failure (not a repository, git unavailable) as an unknown value and carry on. Keep these as separate body Bash calls rather than pre-compute lines: the harness runs a skill's whole pre-compute block as one shell invocation, and a worktree-isolated session refuses a compound command that contains git.
Variables
Arguments: $ARGUMENTS
Purpose
Hard bugs are won or lost in Phase 1. Without a fast, deterministic, agent-runnable signal that says "bug present / bug fixed", every later phase is guessing. Most failed debugging sessions fail because the engineer skipped straight to hypothesising without building a loop.
This skill enforces the discipline. Six phases, each with a clear gate before the next. The middle three (hypothesise → instrument → fix) are mechanical once Phase 1 is solid; the bookends (loop, cleanup) are the load-bearing work.
Scope boundary: this skill starts from an observed failure: UI behaving wrong, a log line that should not appear, a performance regression, a screenshot of a bug, a production symptom. Its first job is to construct a reproduction loop. If the symptom is already a failing test with no reproduction gap, you do not need this skill. Cycle that test directly (reproduce → fix → retest → regression). What /debugging:debug adds over a bare fix loop is a critical edge case: if no correct test seam exists, that absence IS the finding, filed as an architectural recommendation, not a forced test in the wrong place.
Adapting to your environment (graceful degrade)
This skill is self-contained. Where a phase below names an adjacent capability, such as a test-investigation routine, a TDD helper, a headless-browser driver, an architecture-audit agent, an issue tracker, or an outcome-verifier, treat it as optional: if your environment provides that capability (a skill, plugin, agent, or tool), invoke it; otherwise proceed with the inline guidance given here, which stands on its own. Never block a phase because an adjacent tool is absent. Consumer-specific conventions (naming, module layout, banned APIs, work-notes location) come from your own project's CLAUDE.md and tool config. Read them; this skill does not assume them.
Redact secrets in everything you show
Every phase surfaces commands, outputs, and captured artifacts. Redact every secret before it appears in a transcript, work note, or commit. Write <REDACTED> in its place. Build loops that read credentials from env vars, so the secret stays in the environment rather than in the command line you show or the harness you commit. Captured artifacts (HAR files, log dumps, replayed traces) carry auth headers and tokens. Quote only the lines that carry the diagnostic signal. If the redacted output is not enough to diagnose, say so and ask the user rather than widening the quote.
Emit checklist
For any diagnostic run (Phases 1-6), track phase completion. A ready-to-fill checklist is bundled at ${CLAUDE_PLUGIN_ROOT}/skills/debug/templates/checklist.md. If your project has a working-notes or scratch location, copy it there; otherwise track the six phases inline. Phase 4 is SKIPPED when Phase 2 repro conclusively verifies the Phase 3 hypothesis without instrumentation.
Phase 1: Build a tight feedback loop
Before you build the loop, state what you are taking for granted about the failure, so the assumptions are on record before Phase 3 ranks hypotheses against them.
Put the effort of this skill here rather than in the later phases. Once a fast, deterministic, agent-runnable pass/fail signal exists, the cause follows.
Construction strategies: try in roughly this order
- Failing test at whatever seam reaches the bug: unit, integration, e2e
- Curl / HTTP script against a running dev server (bring your dev server up however your stack does)
- CLI invocation with a fixture input, diffing stdout against a known-good snapshot
- Headless browser script (a Playwright-style driver, if available). Drives UI, asserts on DOM/console/network
- Replay a captured trace. Save a real network request / payload / event log to disk, replay through the code path in isolation
- Throwaway harness. Minimal subset of the system (one service, mocked deps) exercising the bug code path with a single function call
- Property / fuzz loop. For "sometimes wrong output", run 1000 random inputs and look for the failure mode
- Bisection harness. If the bug appeared between two known states (commit, dataset, version), automate "boot at state X, check, repeat" so
git bisect runworks - Differential loop. Same input through old-version vs new-version (or two configs), diff outputs
- HITL bash script, last resort. If a human must click, copy the bundled template at
${CLAUDE_PLUGIN_ROOT}/skills/debug/scripts/hitl-loop.template.sh, customize the steps, and ask the user to run it in their terminal (the Bash tool cannot satisfy interactivereadprompts). Have them paste the--- Captured ---KEY=VALUE stdout back into the session so the loop stays structured
Loop-recursion hazard
When the loop IS a test the suite/runner discovers and runs, watch for self-invocation: a test file that invokes the very runner (or pre-push lane) which re-discovers and re-runs it recurses until the box saturates. Each nested run re-triggers the test. The symptom reads as a hang, but it is fork-bombing, not a slow test. Guard with a re-entrancy sentinel: set an env marker before the inner run; a nested invocation that sees the marker exits early. Same pattern for any loop that shells out to a command which re-enters the loop.
Iterate on the loop itself
Treat the loop as a product. Once a loop exists, ask:
- Can it be faster? Cache setup, skip unrelated init, narrow test scope
- Can the signal be sharper? Assert on the specific symptom, not "didn't crash"
- Can it be more deterministic? Pin time, seed RNG, isolate filesystem, freeze network
A 30-second flaky loop is barely better than no loop. A 2-second deterministic loop is a debugging superpower. Per-ecosystem timing-injection patterns (and other I/O-seam abstractions) live in the bundled reference at ${CLAUDE_PLUGIN_ROOT}/skills/debug/reference/ecosystem-debugging.md. See the timing-injection row for your stack. The universal principle: wrap I/O and time sources at the seam where they enter the code so the loop can swap a deterministic stand-in.
Non-deterministic bugs
The goal is not a clean repro but a higher reproduction rate. Loop the trigger 100×, parallelise, add stress, narrow timing windows, inject sleeps. A 50%-flake bug is debuggable; 1% is not. Keep raising the rate until it is.
When you genuinely cannot build a loop
Stop and say so explicitly. List what was tried. Ask the user for: (a) access to whatever environment reproduces it, (b) a redacted captured artifact (HAR file, log dump, core dump, screen recording with timestamps), or (c) permission to add temporary production instrumentation.
Do not proceed to Phase 2 until you have a loop you believe in.
Phase 2: Reproduce
Run the loop. Watch the bug appear.
Confirm:
- The loop produces the failure mode the user described, not a different failure that happens to be nearby. Wrong bug = wrong fix
- The failure is reproducible across multiple runs (or, for non-deterministic bugs, reproducible at a high enough rate to debug against)
- The exact symptom (error message, wrong output, slow timing) is captured so later phases can verify the fix actually addresses it
Do not proceed until the bug is reproduced.
Phase 3: Hypothesise
Generate 3-5 ranked hypotheses before testing any of them. Single-hypothesis generation anchors on the first plausible idea and wastes the next hour.
Each hypothesis must be falsifiable. State the prediction it makes:
"If
<X>is the cause, then changing<Y>will make the bug disappear / changing<Z>will make it worse."
If you cannot state the prediction, the hypothesis is a vibe. Discard or sharpen it.
Ground the ranking in real repo state before you rank: recent commits in the affected area, open issues, architecture decision records, banned-symbol entries, known-issue or quirks notes, and the project instruction files and ADRs nearest the affected file. A hypothesis that contradicts a documented constraint ranks low; one that matches a recent change ranks high.
Show the ranked list to the user before testing. They often have domain knowledge that re-ranks instantly ("we just deployed a change that touches #3"), or know hypotheses they have already ruled out. Cheap checkpoint, big time saver. Do not block on it. Proceed with your ranking if the user is AFK.
Phase 4: Instrument
Each probe must map to a specific prediction from Phase 3. Change one variable at a time.
Tool preference, in order:
- Debugger / REPL inspection if the env supports it. One breakpoint beats ten logs
- Targeted logs at the boundaries that distinguish hypotheses
- Never "log everything and grep". That produces noise that hides the signal
Tag every debug log with a unique short prefix, e.g. [DEBUG-a4f2]. Cleanup at the end becomes a single grep -r "\[DEBUG-a4f2\]". Untagged debug logs survive across PRs; tagged logs die on cue.
Per-ecosystem logging API (idiomatic structured-logger choice for ad-hoc debug instrumentation), banned debug-output APIs, and the required tag-prefix convention live in the bundled reference at ${CLAUDE_PLUGIN_ROOT}/skills/debug/reference/ecosystem-debugging.md. See the logging + banned-output rows for your stack.
Performance branch. For perf regressions, logs are usually wrong. Instead: establish a baseline measurement using your ecosystem's standard timing / benchmark primitives, then bisect against the baseline. Measure first, fix second. Per-ecosystem perf-tooling references (micro-bench libraries, query-plan inspection, profile primitives) live in the reference. See the perf-tooling row for your stack.
Cold-vs-warm + contention. A single timing datapoint taken right after filesystem churn (freshly-created fixtures, a just-cloned repo) or while the box is under load (leaked process trees, a parallel build, antivirus scanning) is cold-cache- and contention-inflated, often by multiples. Before calling a perf number reproducible: re-measure warm, on a quiet box, best-of-N (or worst-of-N for a regression ceiling). A number that drops several-fold on the second clean run was measuring contention, not the code path. Never trust one datapoint after churn.
Phase 5: Fix + regression test
Write the regression test before the fix, but only if there is a correct seam for it.
A correct seam is one where the test exercises the real bug pattern as it occurs at the call site. If the only available seam is too shallow (single-caller test when the bug needs multiple callers, unit test that cannot replicate the chain that triggered the bug), a regression test there gives false confidence.
If no correct seam exists, that itself is the finding. Note it. The codebase architecture is preventing the bug from being locked down. Do not force a test in the wrong place. File the architectural finding in Phase 6 instead.
If a correct seam exists:
- Turn the minimised repro into a failing test at that seam. Follow your project's test naming + structure conventions
- Watch it fail (Red), and confirm it fails for the intended reason. A test that errors on a typo, a bad import, or an unrelated defect is also red, and a fix that turns that red green has not touched the bug. Read the failure message against the root cause you are targeting; if they do not match, repair the test or the reproduction before editing any implementation code
- Apply the smallest fix that addresses the root cause, not the symptom (Green)
- Watch the test pass
- Re-run the Phase 1 feedback loop against the original (un-minimised) scenario. The test passing is necessary but not sufficient
Keep the fix diff focused on the root cause. Leave surrounding cleanup out of this change, even in files you touched. If the fix reveals a design problem, note it for a separate refactor commit or the Phase 6 architectural recommendation.
Phase 6: Cleanup + post-mortem
Required before declaring done:
- Original repro no longer reproduces (re-run the Phase 1 loop)
- Regression test passes (or absence of correct seam is documented as an architectural finding)
- All
[DEBUG-...]instrumentation removed (grep -r "\[DEBUG-returns nothing in source) - Throwaway prototypes deleted (or moved to a clearly-marked sandbox location)
- The hypothesis that turned out correct is stated in the commit message / PR description, so the next debugger learns
- If the loop revealed a recurring class of bug, record it in your project's known-issues / quirks notes
- Confirm the fix outcome: run the mechanical build/test/lint, then check the original symptom is resolved with no regression, and record the evidence. The context that produced the fix converges on approval rather than detection, so beyond those objective checks the outcome verdict should be rendered by an agent that did NOT produce the fix. If your environment has an outcome-verification capability, use it; otherwise dispatch a fresh-context verifier with the symptom, the fix diff, and pass/fail criteria. Boundary:
/debugging:debugDOES the fix + regression test; a verifier VERIFIES the outcome
Then ask: what would have prevented this bug? If the answer involves architectural change (no good test seam, tangled callers, hidden coupling, missing abstraction):
- File the architectural finding with your issue tracker
- If your environment has an architecture-audit agent or a module-deepening review, suggest a focused audit of the affected module
- Make the recommendation after the fix is in, not before. The post-fix view has more information than the pre-fix one
What this skill does NOT do
- Does not ship without a feedback loop. Phase 1 is a hard gate. If a loop cannot be built, that is the report you deliver
- Does not retry blindly. "tried it again and it worked" is not a fix. Intermittent passes mean the root cause is still present
- Does not fix the symptom. A null check at the call site is fixing the symptom; finding why the value is null is fixing the cause
- Does not refactor mid-fix. Keep the diff focused. Architectural findings go to Phase 6
- Does not re-derive a known classification. When the symptom matches a shape your environment already classifies (a known-error taxonomy, a test-investigation routine), lean on that instead of re-deriving it
When to escalate
If after 3 hypothesis-test cycles no candidate is panning out:
- The hypothesis ranking was probably wrong. Go back to Phase 3, re-survey the repo, look for what was missed
- The loop may not be tight enough. Re-iterate Phase 1 (faster, sharper, more deterministic)
- The bug may need redesign rather than a patch. Switch to broader replanning (an architecture/plan-review capability, if available)
- Do not push through a fifth or sixth attempt. That is how technical debt compounds and "fixes" break unrelated code