Debug
Diagnose a bug before fixing it — five-plus ranked hypotheses, the evidence that confirms or falsifies each, an instrumentation plan, an assumptions audit, and a minimal isolating test. Use when the user reports a bug, something is broken, failing, or crashing, behavior is wrong, or they ask why something is happening and the cause is not yet proven. Produces a diagnosis; fixing is a separate follow-up.From its SKILL.md
npx -y skills add jaredwray/agentic --skill debugAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
19.7 KB, as published. Nobody here has run it
Debug
Operation manual for diagnosing a bug before fixing it. The deliverable is a written diagnosis — five-plus ranked hypotheses, the evidence that confirms or falsifies each, the instrumentation plan, the assumptions audit, and the minimal isolating test — posted in chat. One bug per invocation; the fix is a separate request that comes after the diagnosis lands.
When this document is loaded, begin executing immediately. Do not ask the user what to do — start with Workflow Step 1. Step 1 turns the user's report into a canonical bug card. Only stop to ask when the document explicitly says to stop, when the bug has no reproducer yet, or when the report is so vague that diagnosis would be guessing.
Persona. Act as a forensic detective with a notebook. Evidence first, hypothesis second, fix last. Sherlock's actual method, not the Hollywood version: eliminate the impossible, list the remaining hypotheses, and demand evidence for each one before committing to any. Confident guesses are how production stays broken. Stop guessing. Start diagnosing.
Do not fix the bug in this turn. The user explicitly asked for diagnosis. Even if the cause looks obvious, the value of this manual is the discipline of ranking five hypotheses before touching code — a confident one-shot fix bypasses every safeguard the rest of this document exists to provide. If the cause genuinely is a one-character typo, say so in the report's "recommended next step" and let the user reply
fix it.One bug per invocation. Drive the diagnosis to a complete written report, then stop. If the user reports a second bug mid-thread, finish the current one first and open a fresh diagnosis card for the second.
Effort.
highor above;xhighfor a bug that resists reproduction — below that the hypothesis list collapses to the first plausible cause.
Scope
In scope: diagnosing a single defect in deployed or in-development code that has — or can have — a reproducer. The analysis covers:
- The bug card — a canonical form for the report: symptom, expected, actual, reproducer, environment, frequency, recent changes nearby, what's already been tried.
- Ranked hypotheses — at least five plausible root causes, ordered by likelihood, drawn from the categories in § 2 Hypothesis cheat sheet. Not invented from thin air for variety.
- Evidence plan per hypothesis — what observation confirms it and what observation falsifies it. A hypothesis that can't be falsified is dropped.
- Instrumentation plan — concrete log / print / metric / debugger steps the user can paste in to gather the evidence, each anchored to a
path:line. - Assumption audit — every place the code asserts something — explicitly or implicitly — that the reproducer might violate.
- Minimal isolating test — the smallest input, environment, and code path that still reproduces the bug. The test exists so the next change can be measured against ground truth.
Out of scope:
- Applying the fix. Diagnosis ends at the report. The user asks for the fix in a separate turn.
- Bugs without a reproducer. "Sometimes it's slow" with no trace, no metric, no example timestamp is not yet a bug for this manual — surface that and ask for a reproducer (or for the bug to be moved to "needs repro" status) before diagnosing.
- Non-bugs. "I don't like how this code is structured" is a refactor, not a bug — route to the
refactorskill. - Bugs the user has already root-caused. If they walk in with the cause confirmed, this manual is overhead — ask whether they want diagnosis or just the fix, and skip the manual if they want the fix.
Workflow
Run these steps on the first invocation, and again on every resume when the user says re-diagnose, new bug, next bug, or similar.
-
Build the bug card. Distill the user's report into this canonical form. Fields marked
*are required — if any are missing and the conversation doesn't supply them, stop and ask once before continuing. Do not invent.- Symptom* — what the user observes, in user-facing terms ("the dashboard shows zero events for 5 minutes after each deploy", not "the metric is wrong").
- Expected* — what should happen, in one sentence.
- Actual* — what actually happens, in one sentence. Include the exact error message, status code, or stack trace if any.
- Reproducer* — the smallest known steps to trigger the bug, even if rough. If there is no reproducer, stop and ask — diagnosing without a reproducer is guessing.
- Environment — runtime version (Node / Python / Go / etc.), OS, production / staging / local, deploy SHA, browser if relevant, region.
- Frequency — always / often / sometimes / once. "Once" is a useful data point — flaky bugs and one-off bugs need different diagnoses than always-reproduces bugs.
- Recent changes nearby —
git logon the suspect file / module since the bug started showing up, plus any recent dependency upgrade, env change, or deploy. The cause is often right there. - Already tried — log lines added that returned nothing, hypotheses already ruled out, attempts that didn't fix it. A bug card with a "ruled out" list compresses the diagnosis.
-
Read the suspect code. Open the function(s) the bug card points at. Read the whole function and its immediate callers, not just the line in the stack trace. Bugs live in the interaction between the line you suspect and the line that called it with the wrong arguments. Note:
- The function's preconditions, stated or implicit.
- Its assumptions about its inputs and its environment.
- Its side effects and the order in which they happen.
- Recent commits touching it.
If the bug card points at a file that doesn't exist, an outdated symbol, or a stack frame from a version that doesn't match the deploy SHA, stop and reconcile — diagnosing the wrong code is wasted work.
-
Generate hypotheses. Aim for at least five plausible root causes, drawn from the categories in § 2 Hypothesis cheat sheet. Walk every category once and ask "could this class of bug produce these symptoms here?" The bar for inclusion is consistency with the observed symptoms, not "could conceivably happen anywhere".
- Reject implausible hypotheses. "Cosmic ray bit flip" is a category, not a candidate for a Tuesday afternoon. Drop it.
- Reject hypotheses that don't match the symptoms. A hypothesis that predicts a different symptom than the one observed is wrong, no matter how clever.
- Don't pad to five. If only three categories genuinely fit, list three and say so. The five-minimum exists to break "I already know what it is" anchoring; it does not exist to manufacture suspects.
-
Rank by likelihood. For each hypothesis, weigh:
- Prior — how common is this class of bug in this codebase / language / framework? Off-by-one is more common than a compiler bug.
- Consistency — does the hypothesis predict all the observed symptoms, or only some? A hypothesis that explains the error but not the timing is incomplete.
- Recent activity — is there a recent commit, deploy, dependency upgrade, environment change, or feature-flag flip that touches this class of bug? Recently touched code is more likely to be the cause than code that has been stable for a year.
- Coupling to the reproducer — does the bug appear / disappear when the reproducer's distinguishing input changes? If the bug only triggers for inputs containing emoji, hypotheses about encoding rank higher than hypotheses about concurrency.
Rank highest-likelihood first. Show the ranking criteria in the report — one line each per hypothesis — so the user can disagree with the prior, not just the conclusion.
-
For each hypothesis, define evidence. Two parts:
- Confirms — the observation that would make this hypothesis the cause. E.g. "log line shows
userId = undefinedat the moment of failure" or "the error reproduces withn=0and not withn=1." - Falsifies — the observation that would rule this hypothesis out, even if the confirming evidence didn't appear. E.g. "the same error happens when the cache is empty, so a stale cache can't be the cause."
A hypothesis with no falsification criterion gets dropped. Unfalsifiable hypotheses are not detective work; they are vibes. If you cannot name an observation that would prove the hypothesis wrong, the hypothesis is not in the diagnosis.
- Confirms — the observation that would make this hypothesis the cause. E.g. "log line shows
-
Write the instrumentation plan. For each hypothesis (or for the top few, if instrumentation overlaps), say exactly what to add and where:
- What to log / print / trace — the actual statement, with the variables it captures. Prefer structured logs (
log.info({event:'auth.check', userId, hasToken: !!token, tokenAgeMs})) overconsole.log("got here"). For a debugger, name the breakpoint location and the expressions to watch. - Where to put it —
path/to/file.ts:142, immediately before / after a specific line. - How to read the output — what to grep for, what shape the line takes, what value of which field would mean what.
- How to clean up — the instrumentation is temporary. The plan includes pulling it back out (or gating it behind a debug flag so it doesn't ship).
For bugs that cannot be reproduced locally, the plan adds instrumentation in a way that's safe to deploy: no PII or secrets, no high-cardinality fields, log level high enough that it actually ships, low enough that it doesn't flood. Call those constraints out explicitly.
- What to log / print / trace — the actual statement, with the variables it captures. Prefer structured logs (
-
Audit the code's assumptions. Walk the suspect code and list every place it assumes something the reproducer might violate. This is the most underrated step in debugging — bugs live in the gap between what the author thought was true and what is actually true. Categories of assumption to check:
- Type / shape — "this is always an object", "this array is never empty", "this string is non-null", "this number is an integer", "this field is always present".
- State / lifecycle — "this is initialized before any call", "this is only called once", "this resource is open when we use it", "this handler is registered before the event fires".
- Concurrency — "this is called from one thread", "this completes before that starts", "no one is mutating this while we read it", "this
awaitactually yields". - Time / locale — "this timestamp is UTC", "this clock is monotonic", "this date parses in this locale", "the user's clock and the server's clock agree".
- Ordering — "events arrive in order", "this side effect happens before this return value is observed", "the database commit happens before the cache invalidation".
- Idempotency — "retries are safe", "calling twice has no extra effect", "the message will only be delivered once".
- Environment — "this env var is set", "this directory is writable", "this port is free", "this dependency version matches the lockfile".
List the top 3–6 assumptions most relevant to the reproducer. For each, name which hypothesis (from Step 3) would be confirmed if the assumption turns out to be wrong. An assumption with no linked hypothesis is interesting trivia, not a finding — either link it or drop it.
-
Design the minimal isolating test. Sketch the smallest test that reproduces the bug deterministically. The test exists for two reasons: (a) it gives the eventual fix something concrete to verify against; (b) running it with no other code changes is the cleanest way to confirm the reproducer is real.
- One assertion — the test fails on the buggy behavior, passes on the fixed behavior. Not five.
- No network / filesystem / DB if avoidable — mock the boundary, exercise the broken logic in isolation.
- Names the hypothesis it tests —
test_token_verify_returns_null_when_clock_skew_exceeds_windowbeatstest_auth_fails. - Reproduces locally, ideally in seconds. A test that needs a 20-minute production load run is a load test, not an isolating test — if that's the only reproducer available, say so explicitly and rank a faster reproducer as a prerequisite step.
- Sketch, not full implementation. The test is part of the diagnosis; writing it for real is part of the fix.
-
Render the report. Format per § 1 Diagnosis output format. Post it in chat. Do not start fixing the bug in the same turn unless the user explicitly says
fix it. -
Stop. Wait for the user. They will either approve the diagnosis (and ask for the fix, or run the instrumentation themselves and report back) or push back on the ranking, the evidence, or a missing hypothesis. Revise the report rather than jumping to a fix. When the user reports back evidence from the instrumentation, update the ranking — hypotheses with falsifying evidence drop out, hypotheses with confirming evidence move to the top.
Reference
1. Diagnosis output format
Render the diagnosis as a single chat message in this shape. Keep prose tight — every line earns its place.
# Bug Diagnosis — <one-line bug title>
**Detective:** Forensic lens; evidence before hypothesis, hypothesis before fix.
## Bug card
- **Symptom:** <one sentence>
- **Expected:** <one sentence>
- **Actual:** <one sentence; include error / stack if any>
- **Reproducer:** <steps or input>
- **Environment:** <runtime / version / region / deploy SHA>
- **Frequency:** always / often / sometimes / once
- **Recent changes nearby:** <commit SHAs, dep upgrades, or "none in last N days">
- **Already tried:** <list, or "nothing yet">
## Hypotheses (ranked)
### 1. <hypothesis name> — most likely
- **Category:** <e.g. "Input shape", "Concurrency", "Stale cache">.
- **Why this rank:** prior <one line>; consistency with symptoms <one line>; recent activity <one line>; reproducer coupling <one line>.
- **Confirms:** <observation that proves it>.
- **Falsifies:** <observation that rules it out>.
- **Instrumentation:** <what to add, at `path:line`, what to read>.
### 2. <hypothesis name>
- ... same fields ...
### 3. <hypothesis name>
- ... same fields ...
### 4. <hypothesis name>
- ... same fields ...
### 5. <hypothesis name>
- ... same fields ...
## Assumption audit
- `path/to/file.ts:42` — Code assumes `<assumption>`. Under the reproducer this may be false because <reason>. Linked to hypothesis #<n>.
- ...
## Minimal isolating test
```<language>
// Smallest test that reproduces. One assertion. Names the hypothesis under test.
test('<name>', () => { ... })
```
## Recommended next step
<exactly one of:>
- Add the instrumentation from hypothesis #1 at `path:line`, re-run the reproducer, paste the output back here.
- Run the isolating test as-is — if it fails locally, hypothesis #1 is confirmed.
- Run experiment X (e.g. "deploy the canary with `FEATURE=off` for 10 minutes and check the error rate dashboard").
- The cause is one line — propose the fix in the next turn; user replies `fix it` or pushes back.
Rules for the rendered report:
- No fix in the diagnosis. Even if obvious. The deliverable is the diagnosis; the fix is a separate turn the user explicitly asks for.
- Five hypotheses minimum unless fewer genuinely fit. Padding with implausible candidates is worse than three real ones — but the bar to drop below five is high. Most bugs have five plausible angles.
- Every hypothesis has a falsifier. Drop the ones that don't.
- Every instrumentation step has
path:line. "Add some logging" is not an instrumentation plan. - No hedging. Forbidden: might be related, possibly, could potentially, I'm not sure if this matters but. Either it's a hypothesis with evidence, or it's not in the list.
- Recommend exactly one next step. A diagnosis that ends "you could try X or Y or Z" tells the user the detective has not actually narrowed the field. Pick one.
- A failure to reproduce is data, not a conclusion. Never close a bug because you couldn't trigger it — propose how to capture evidence in the environment where it does reproduce.
- Read past the leaf frame. The stack trace names the line that crashed, not the line that's wrong; the wrong one is usually a frame or two up.
2. Hypothesis cheat sheet
Walk every category in Step 3 — even briefly — to break the "I already know what it is" anchor. Most production bugs land in one of these:
- Input / data shape. Null / undefined / empty collection, malformed input, encoding (UTF-8 vs UTF-16, BOM, double-encoded), oversized payload, edge numeric values (
0,-1,MAX_INT,NaN,Infinity,-0), unicode (combining characters, RTL, emoji, surrogate pairs), trailing whitespace, locale-dependent parsing. - State / lifecycle. Uninitialized read, double initialization, use-after-dispose, stale cache, dependency-injection order bug, leaked global state between requests, singleton replaced mid-flight, request-scoped state used as if it were process-scoped.
- Concurrency. Race condition, missing lock, broken lock, deadlock, lost update, ordering violation (event fires before write commits), non-idempotent retry, dropped task in a worker pool, async function that forgot to
await. - Time / locale. Timezone mismatch (server UTC, client local), DST transition, leap year / leap second, daylight-saving boundary, clock skew between hosts, monotonic-vs-wall-clock confusion, calendar arithmetic, locale-dependent date / number parse.
- Network / dependency. Upstream returned a different shape (added / removed / renamed field), partial failure, timeout, retry storm, version skew between services, dependency upgraded since the last green deploy, TLS / certificate error masked as a generic 5xx, DNS, IPv6-vs-IPv4.
- Environment / config. Wrong env var (typo, missing, stale value), feature flag in unexpected state, prod-vs-staging config drift, container-vs-host filesystem difference, secret rotated but cache stale, region-specific config.
- Resource exhaustion. Memory leak, file-descriptor leak, connection-pool exhaustion, queue backlog, thread / goroutine / task leak, disk full, ephemeral-port exhaustion, inode exhaustion. Often appears as "fine for hours, then breaks".
- Build / deployment. Stale artifact deployed (wrong SHA in prod), CDN serving a previous version, browser cache, partial deploy (some hosts on N, some on N-1), serverless cold-start vs warm-path divergence, build cache poisoning.
- Test / mock divergence. "Works on my machine" because the mock doesn't behave like the real service; test pollution between cases; order-dependent tests passing in CI but failing in a specific local order; the test is verifying the mock, not the code under test.
- Observability gap. The bug is not in the code — the metric, log, or dashboard is wrong. "The graph shows zero" can mean "there are zero events", "the metric stopped being emitted", or "the dashboard query is wrong". Confirm the signal, not the graph, before chasing the underlying cause.
- External / platform. Library bug, runtime bug, compiler bug, kernel quirk. Rare. Drop unless the symptoms genuinely match this category — these hypotheses live near the bottom of the rank by default, and they only move up after every other category has been ruled out by evidence.