pedrohcgs/diagnose
Root-cause a failing or wrong empirical result with a disciplined reproduce → minimise → hypothesise → instrument → fix loop, instead of guessing-and-poking. Use when the user says "why is my regression wrong", "this number changed", "my script errors out", "the result won't reproduce", "debug this", "this estimate looks wrong", or "it worked yesterday". Tuned for research code (R/Stata/Python): type coercion, NA/merge blow-ups, factor levels, clustering/SE choices, weighting, collinearity/convergence, seeds, package-version drift. Use `--no-fix` to localize the root cause without editing shared or load-bearing files.
npx skills add https://github.com/pedrohcgs/claude-code-my-workflow --skill diagnose
Find *why* an analysis errors, returns the wrong number, or won't reconcile — with a structured debugging loop rather than scattershot edits. Adapted from the diagnose pattern in mattpocock/skills, reshaped for empirical research code where the bug is usually a *silent* wrong number, not a crash.
The discipline: never edit before you can reproduce, and never fix before you can explain. A guessed fix that makes the symptom disappear without a named root cause is how a wrong number gets *laundered* into a published table.
/audit-reproducibility and you need to localize *which* step drifted.Diagnose is symptom-driven and single-target: ONE wrong number / ONE failing run. Use a sibling instead when the job is different:
/audit-reproducibility — verify *all* numeric claims in a manuscript against current code (claim-driven, whole-paper). If you have one FAILing claim and want to localize which pipeline step produced it, /audit-reproducibility hands off *to* /diagnose; if you want to re-check every table number, start there./review-r — code-quality review with no specific symptom./capture-environment — snapshot the environment when version/seed drift is the suspect.State the bug as a falsifiable gap before touching anything:
replication-protocol.md.)If expected/actual can't be stated, the task is *understanding*, not diagnosis — stop and clarify first.
A bug you can't reproduce on demand can't be fixed, only hidden.
sessionInfo() / pip freeze / Stata version (lean on /capture-environment).Shrink until the bug sits in the open:
The MWE is the deliverable even if the fix is later trivial: it's what makes the root cause undeniable.
List candidate causes *before* testing any — a written list beats poking because it prevents fixating on the first idea. For research code, walk the usual suspects (all of these run cleanly with no error message — they are silent-wrong-number bugs):
TRUE/FALSE ↔ 1/0.NA dropped silently, na.rm flipping a mean, listwise deletion changing the sample mid-pipeline.For a genuinely ambiguous bug, fan out the top competing hypotheses to parallel Task subagents (one per hypothesis, context: fork), each instructed to *try to confirm its own cause on the MWE* and report back — the loop-first analogue of asking three colleagues at once (see orchestrator-protocol.md).
Each hypothesis (whether tested by hand or by a fan-out Task) returns {hypothesis, evidence for, evidence against, confidence, one-line conclusion}. Then:
Test the ranked hypotheses cheaply:
log2(n) steps, not n.git bisect is fine here — it never discards work; the destructive git commands are blocked by git-guardrails.py, this is not one of them.)str() / summary() for types & NA patterns; row & column counts *before and after* every transform; table(factor) to catch a silently dropped level; cor() / VIF for unexpected collinearity; weight diagnostics range(w), sum(w), table(is.na(w)); and the regression's convergence flag. The stage where a count drops unexpectedly, a factor level vanishes, correlation jumps, or weights go sparse is the culprit stage.End Phase 4 with a one-sentence root cause naming the exact line/step and mechanism.
Confidence gate (the anti-laundering rule): do not apply a fix unless the root cause is named and its mechanism is explicit. If Phase 3b left a near-tie, behave as --no-fix: report the candidates and ask. Editing research code on an unproven hypothesis is exactly the laundering this skill exists to prevent.
Unless --no-fix is set:
actual == expected within the Phase-0 tolerance./audit-reproducibility).| Bug class | One-line guard |
|---|---|
| Types & coercion | stopifnot(is.numeric(x)) after read |
| Missingness | explicit na.rm = FALSE; stopifnot(sum(is.na(x)) == 0) |
| Joins & shape | record nrow pre-merge; stopifnot(nrow(out) == nrow(left)) for a 1:1 join |
| Weighting | stopifnot(abs(sum(w) - 1) < 1e-8) or !anyNA(w) |
| Convergence | assert the optimizer/model convergence flag is OK before using estimates |
| Sample | one explicit filter() with a stated reason, not a mid-pipe drop |
| Environment | pin versions in renv.lock; set.seed() at the top of each script |
Propose the guard; don't silently install a test suite.
With --no-fix, stop after the root cause is named and report it for the user to fix by hand.
A staggered-DiD ATT jumped from −0.043 to −0.071 after a data refresh; nothing in the spec changed.
# Phase 1 — reproduce: set.seed(1); same script, same number every run. Red is stable.
# Phase 2 — MWE: one cohort, two periods still shows the jump.
# Strip to: read panel -> merge covariates -> feols(). Bug survives the merge step.
# Phase 4 — instrument: row counts before/after each step
nrow(panel) # 12,400 (expected)
nrow(merge(panel, covars, by="id")) # 12,933 <-- inflated! a many-to-many merge
# Root cause: the refresh left duplicate covars rows for a subset of ids; the
# join fans those ids out, 12,400 -> 12,933 (+533 rows), re-weighting the ATT
# toward the duplicated units.
# Phase 5 — minimal fix at the root (dedup the key), NOT a downstream row filter:
covars <- covars[!duplicated(covars$id), ]
# re-run: ATT back to -0.043 within tolerance; full pipeline re-checked, no other number moved.
# Prevention (Joins & shape guard):
stopifnot(nrow(merge(panel, covars, by = "id")) == nrow(panel))
Write a short diagnosis to quality_reports/diagnoses/YYYY-MM-DD_<slug>.md (create the directory first: mkdir -p quality_reports/diagnoses). These reports may contain real data values and file paths — they are project-internal and gitignored, like session logs. Include:
--no-fix, the recommended change).Plus a chat summary leading with the one-line root cause.
The usual-suspects model is illustrated in R but the bug *classes* are language-neutral; the diagnostic idioms differ:
anyNA() / table(is.na(x)); factors silently drop unused levels; set.seed(); sessionInfo().tab v, missing and explicit ./.a–.z extended missing; set seed; version; weights as [fw=] vs [pw=] vs [aw=] is a frequent silent bug.df.isnull().sum(); numpy.nan ≠ None; pandas vs numpy NaN handling differ; np.random.seed() / a passed random_state; pip freeze.(Forkers in other fields: the five structural classes — Types, Missingness, Joins, Sample, Environment — are discipline-neutral; the econometric suspects above are the worked instance.)
| Outcome | Action |
|---|---|
| Root cause NAMED (high confidence), fix applied, re-verified | report root cause + diff + prevention |
| --no-fix | stop at a named root cause; write the report, make no edit to source |
| Phase 0 blocked (no statable expected/actual) | halt, ask for the expected value — diagnosis needs a target |
| Phase 1 blocked (cannot reproduce / nondeterminism) | report the nondeterminism *as* the finding (it is the bug class) + how to make the analysis deterministic; do not edit blindly |
| Phase 3b near-tie / <50% | report the competing hypotheses and ask the user; do not apply a fix |
--no-fix — Diagnose only: run through naming the root cause (Phases 0–4) and write the report, but make no edit to source. Use when you want to apply the fix yourself, or when the file is shared/load-bearing and an automated edit is inappropriate..claude/skills/review-r/SKILL.md — code-quality review with no specific symptom (diagnose is symptom-driven)..claude/skills/audit-reproducibility/SKILL.md — verify all numeric claims against code; diagnose localizes a *single* failing one (and is the natural hand-off from a FAIL)..claude/skills/capture-environment/SKILL.md — snapshot the environment when version/seed drift is the suspect..claude/rules/replication-protocol.md — the tolerance contract that defines "same number", and the "If Mismatch" hand-off to this skill..claude/rules/orchestrator-protocol.md — the fan-out primitive used for competing-hypothesis testing in Phase 3./review-r. Diagnose needs an expected-vs-actual gap to chase./audit-reproducibility. Diagnose fixes one bug deeply./commit's job.Take pedrohcgs/diagnose from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.