
Debugging
- 108 installs
- 31 repo stars
- Updated August 4, 2026
- iliaal/ai-skills
Systematic root-cause debugging with verification for errors, stack traces, broken or flaky tests, and regressions, refusing fixes before the root cause is found.
About
The debugging skill enforces identifying the root cause with evidence before proposing any fix, using backward tracing, differential analysis, and git bisect. A developer uses it for errors, stack traces, broken or flaky tests, and regressions.
- Iron Law: never propose a fix without the root cause
- Root cause stated with file:line evidence at least two levels deep
Debugging by the numbers
- 108 all-time installs (skills.sh)
- +6 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #239 of 596 Debugging skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/iliaal/ai-skills --skill debuggingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 108 |
|---|---|
| repo stars | ★ 31 |
| Last updated | August 4, 2026 |
| Repository | iliaal/ai-skills ↗ |
What it does
Systematic root-cause debugging with verification for errors, stack traces, broken or flaky tests, and regressions, refusing fixes before the root cause is found.
Files
Debugging
The Iron Law
Never propose a fix without first identifying the root cause. "Quick fix now, investigate later" is forbidden -- it creates harder bugs. This applies ESPECIALLY under time pressure, when "just one quick fix" seems obvious, or when multiple fixes have already failed. Those are the moments this process matters most.
Trivially obvious bugs are their own root cause -- state the cause and fix directly. A bug is trivially obvious only when the cause is in the error message (e.g., ModuleNotFoundError: no module named foo, a typo in a string literal). If the error shows where something fails but not why (e.g., TypeError: Cannot read 'id' of undefined), it is not trivially obvious -- investigate why the value is undefined.
Root Cause Analysis
Root cause identification is the core deliverable of debugging -- not the fix itself.
- Trace backward: Start at the symptom, walk the call chain in reverse to find where behavior diverges from expectation
- Differential analysis: Compare working vs broken state across dimensions (code version, data, environment, timing, configuration)
- Regression hunting: Use
git bisectto pinpoint the exact commit that introduced the issue - Evidence-based: Document root cause with
file:linereferences, log output, and concrete reproduction proof. Root cause = the earliest point where behavior diverged from expectation, stated with evidence at least two levels deep (not just "it failed here" but "it failed here because X was null, and X was null because Y never set it") - Competing hypotheses: When the cause is ambiguous, generate multiple hypotheses and rank by evidence strength (see Escalation section below)
Environment Diagnostics
Capture environment state with bash collect-diagnostics.sh (script). Use during differential analysis or attach to bug reports. See specialized-patterns.md for details.
Process
0. Read the error. Read the full error message, stack trace, and line numbers before doing anything. Error messages frequently contain the exact fix. Don't skim -- read the entire output.
1. Reproduce -- build a feedback loop, then make the bug consistent. The loop is the deliverable of this step, not the analysis. Without a fast, deterministic signal that says "broken / fixed," every later step is guesswork.
A loop already provided? Run it before touching source. If the workspace already has a test file, or the report says "run X to see the failure," that command is your feedback loop: run it before reading source files or forming hypotheses (after reading the error in Step 0), and record the RED output. Do not edit source until you have observed a failing run this session; without RED first, you cannot prove your fix changed anything.
Pick the cheapest loop that triggers the bug:
- Failing test (preferred -- becomes the regression test in step 6)
curlscript orhttpieinvocation against a local server- CLI harness or REPL session
- Headless browser script (Playwright, Puppeteer)
- Log replay against a captured request body
- Throwaway harness in
/tmp/-- delete when done - Property-based test (Hypothesis, fast-check)
- HITL bash session with manual reproduction steps documented
(git bisect and differential analysis are strategies applied during Step 5 and Pattern Comparison, not feedback loops -- they answer "which change broke it?" or "how does broken differ from working?" rather than "is it broken right now?")
If the bug is intermittent, run the loop N times under stress or simulate poor conditions (slow network, low memory) until it triggers reliably.
Cannot build a loop? Stop. State exactly what is missing -- access, credentials, artifacts, repro steps -- and ask the user. Do not proceed to investigate without a signal; you will pattern-match instead of debug.
2. Form initial hypotheses -- before investigating broadly, form 2-3 hypotheses based on the reproduction. What are the most likely causes given the symptoms? This focuses the investigation on plausible paths rather than searching aimlessly.
For each hypothesis, cite at least one concrete observation that supports it: a runtime variable value, a log line, an instrumented boundary capture, a behavior delta against a working comparison case, or a specific code reference. "X seems off" is not evidence; "X equals null at line 42 because Y was never initialized in the path that runs under condition Z" is. Hypotheses without grounding observations are theorizing -- go back and instrument until you have an observable signal (extend the Step 1 loop, or add Step 4 boundary captures).
3. Reduce -- strip the reproduction to the minimal failing case. Remove unrelated code, data, and configuration until removing one more piece makes the bug disappear. That remaining piece is the trigger.
4. Investigate -- trace backward through the call chain from the symptom. Compare working vs broken state using a differential table (environment, version, data, timing -- what changed?).
Route the first move by bug class before instrumenting. Visual/rendering bugs want a static read of the render path and computed styles, not logs; behavioral/async/state/lifecycle bugs want a probe added now as part of the hypothesis; pure-logic bugs need only a careful read. See specialized-patterns.md for the routing and the "write the question before the log" rule.
Multi-component systems (CI -> build -> deploy, API -> service -> DB): before proposing fixes, instrument each component boundary:
- Log what data enters the component
- Log what data exits the component
- Verify environment/config propagation across the boundary
Run once to gather evidence showing WHERE it breaks, then investigate that specific component. Use console.error() (not logger, which may be suppressed in tests). Log BEFORE the dangerous operation, not after it fails. Include context: cwd, env vars, new Error().stack.
Pre-existing failure proof: Before claiming a test failure is "not related to our changes," prove it. Run git stash && [test command] on clean state to confirm the failure exists on the base branch. Pre-existing without receipts is a lazy claim.
Before external searches (web, docs, forums): strip hostnames, IPs, file paths, SQL fragments, and customer data from the query. Raw stack traces leak privacy and return noise.
5. Hypothesize and test -- one change at a time. If a hypothesis is wrong, fully revert before testing the next. Use git bisect to find regressions efficiently. Scope lock: after forming a hypothesis, identify the narrowest affected directory or file set. Do not edit code outside that scope during the debug session. If the fix requires changes elsewhere, update the hypothesis first.
6. Fix and verify -- create a failing test FIRST, then fix. Run the test. Confirm the original reproduction case passes. No completion claims without fresh verification evidence (see ia-verification-before-completion).
Reproduce-passes is not fixed. Stopping the exact reproduction case is easy; the bad state is often still reachable from a nearby variant when the fix landed at the crash site, not the root cause. Before declaring done, run the bypass self-check: name one input variation that reaches the same bad state without tripping your change. If you can, the fix is at the wrong layer -- return to root cause. For security-relevant bugs, escalate to an adversarial re-attack: a fresh-context agent, blind to your fix reasoning, attacks the patched code to find a variant that still triggers it.
Suppression is not a fix. The bypass self-check assumes the fix attacks the bug -- confirm it actually does. A fix that swallows the error (try/except: pass, a blanket catch), disables the failing assertion, or special-cases the reproduction input makes the signal disappear while the defect lives on. A global swallow even passes the bypass check, because nothing reaches the bad state anymore. The fix must change behavior at the root cause, not hide the symptom.
Trim to the minimal diff. After the fix verifies, run a fresh-context pass asked only to "simplify to the smallest change that fixes the root cause." The fixing session is anchored to its own reasoning and over-reaches; a blind pass reliably finds the trim points without reintroducing the bug.
On a failed fix: return to Step 5 and explicitly invalidate the current hypothesis before forming a new one. State what evidence ruled out the prior hypothesis, then form a new hypothesis with its own grounding observation. Do not retry variants of the same theory ("maybe it was the other branch", "let me also catch this case") -- that is rationalization, not iteration. The Three-Fix Threshold below counts cycles, not retries within a single broken theory.
Debug Report
Emit after every resolved bug. For non-trivial production bugs, also write a full Postmortem (see below).
After resolving, output a structured report:
SYMPTOM: [What was observed]
ROOT CAUSE: [Why it happened -- file:line with evidence]
FIX: [What changed]
EVIDENCE: [Verification output proving the fix]
REGRESSION: [Test added to prevent recurrence]
RELATED: [Prior bugs in same area, known issues, architectural notes]
STATUS: DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT (definitions in `ia-verification-before-completion`)Three-Fix Threshold
After 3 failed fix attempts, STOP. An attempt = one complete hypothesis-test cycle (form hypothesis, make minimal change, verify). The problem is likely architectural, not a surface bug. Escalate to the user before attempting further fixes. Step back and question assumptions about how the system works. Read the actual code path end-to-end instead of spot-checking.
Architectural problem indicators -- signals the bug is structural, not a surface fix:
- Each fix reveals new shared state or coupling you didn't expect
- Fixes require massive refactoring to implement correctly
- Each fix creates new symptoms elsewhere in the system
No root cause found: If investigation is exhausted without a clear root cause, say so explicitly. Document what was checked, what was ruled out, and what instrumentation to add for next occurrence. An honest "unknown" with good diagnostics beats a fabricated cause.
Escalation: Competing Hypotheses
When the cause is unclear across multiple components, use Analysis of Competing Hypotheses (ACH). Generate hypotheses across failure categories, collect evidence FOR and AGAINST each, rank by confidence, and investigate the strongest first.
See competing-hypotheses.md for the full methodology: six failure categories, evidence strength scale, confidence scoring, and anti-patterns.
Intermittent Issues
For race conditions, deadlocks, resource exhaustion, and timing-dependent bugs, see specialized-patterns.md. Key signals: shared mutable state, check-then-act, circular lock acquisition, connection pool exhaustion under load.
Defense-in-Depth Validation
After fixing, validate at every layer -- not just where the bug appeared. See defense-in-depth.md for the four-layer pattern (entry, business logic, environment, instrumentation) with examples.
Common Patterns and Bug Triage
For the recurring-pattern catalog (async ordering, stale state, stale build artifacts, recurring fix site) and the severity-vs-priority triage heuristic when multiple bugs compete, see specialized-patterns.md.
Root Cause Tracing
When a bug manifests deep in the call stack, resist fixing where the error appears. Trace backward through the call chain to find the original trigger, then fix at the source. See root-cause-tracing.md for the full technique with stack instrumentation patterns and test pollution detection.
Pattern Comparison
When the cause isn't obvious, find working similar code in the codebase and compare it structurally with the broken path. Read the working reference implementation completely -- don't skim. List every difference between working and broken, however small. Don't assume any difference can't matter. The bug is in one of them.
Anti-Patterns and Red Flags
When you catch yourself doing or thinking these things, stop and return to Step 1 (Reproduce):
| What You're Doing / Thinking | What It Really Means |
|---|---|
| Shotgun debugging / "I see the problem, let me fix it" / "It's probably X" | Reasoning is not evidence. Form a hypothesis, make one change, test, revert if wrong. Trace the actual execution path. |
| Ignoring intermittent failures ("works on my machine") | Instrument and reproduce under load. Isolation success doesn't explain integration failure. |
| "I'll clean up the debugging later" | Remove diagnostic code now or it ships to production. |
| "This failure is pre-existing, not related to our changes" | Prove it: run the test suite on the base branch. No receipts = no claim. |
| "The test is wrong, not the code" | Verify before dismissing. Read the test's intent. If the test is genuinely wrong, fix it with a clear rationale, not a silent update. |
| "Reference too long, I'll adapt the pattern" | Partial understanding guarantees bugs. Read the working example completely and apply it exactly. |
See specialized-patterns.md for anti-pattern signals and specialized debugging patterns.
Verify
- Root cause identified with
file:lineevidence (not just "it failed here") - Regression test exists and fails without the fix, passes with it
- Bypass self-check run: no variant input reaches the same bad state without tripping the fix (for security-relevant fixes, adversarial re-attack found no bypass)
- Debug Report emitted with all seven fields (SYMPTOM, ROOT CAUSE, FIX, EVIDENCE, REGRESSION, RELATED, STATUS)
- No diagnostic instrumentation left in code (
git diffshows no leftover logging)
Integration
This skill is referenced by:
/ia-work-- during task execution for bug investigationia-writing-tests-- creating failing tests to reproduce bugsia-verification-before-completion-- before claiming a bug is fixedia-bug-reproduction-validatoragent -- follows Root Cause Analysis methodologyia-infrastructure-engineeragent -- follows Postmortem template for production incidentsia-reproduce-bugcommand -- automated bug reproduction workflow
Postmortem
For non-trivial production bugs, write a lightweight postmortem (timeline, root cause, impact, fix, prevention). See specialized-patterns.md for the template.
Analysis of Competing Hypotheses (ACH)
When the root cause is unclear -- especially across multiple components -- systematic hypothesis analysis prevents premature commitment to an incorrect explanation.
When to Use
- Multiple plausible explanations for a failure
- Bug spans component boundaries (API -> service -> DB)
- Three-Fix Threshold reached (3 failed attempts)
- Intermittent failures with no clear reproduction pattern
Six Failure Categories
Generate hypotheses across these categories. Most bugs start as one category but have root causes in another.
| Category | Symptoms | Example |
|---|---|---|
| Logic error | Wrong output for valid input, off-by-one, incorrect branching | <= vs < in loop boundary |
| Data issue | Unexpected null, wrong type, stale cache, encoding mismatch | JSON field renamed upstream, cached value from previous schema |
| State problem | Race condition, leaked global state, order-dependent initialization | Test passes alone, fails in suite due to shared DB state |
| Integration failure | Contract mismatch at boundary, wrong endpoint, auth expired | Service A sends user_id, service B expects userId |
| Resource exhaustion | Timeout, OOM, connection pool depleted, disk full | DB pool max hit under load, queries queue indefinitely |
| Environment | Config drift, wrong version, missing dependency, OS difference | Works on macOS, fails on Linux due to case-sensitive filesystem |
Evidence Strength Scale
Not all evidence is equal. Rank each piece:
| Strength | Type | Example |
|---|---|---|
| Strong | Direct observation | Stack trace pointing to exact line, failing test output |
| Medium | Correlational | Bug appeared after deploy X, timing correlates with load spike |
| Weak | Testimonial | "I think I saw this before when..." (no logs or reproduction) |
| Variable | Absence of evidence | "This component has no errors in its logs" (absence != proof) |
The ACH Process
1. List hypotheses
For each failure category, generate at least one hypothesis. Be specific: "data issue" is not a hypothesis; "the user.email field is null because the upstream API changed its response format" is.
2. Collect evidence
For each hypothesis, gather evidence FOR and AGAINST:
H1: Race condition in session initialization
FOR: Intermittent (strong), only under concurrent requests (medium)
AGAINST: Single-threaded test also fails (strong)
→ WEAKENED by counter-evidence
H2: Stale config cached after deploy
FOR: Timestamp of first failure matches deploy (medium), restart fixes it (strong)
AGAINST: None found
→ STRONGEST candidate3. Score confidence
| Confidence | Meaning | Action |
|---|---|---|
| >80% | Strong evidence, weak counter-evidence | Proceed with fix, but verify |
| 50-80% | Mixed evidence | Investigate further before fixing |
| <50% | Weak or contradictory evidence | Do NOT attempt a fix yet |
4. Investigate the top hypothesis
Test the highest-confidence hypothesis first. One change at a time. Fully revert if wrong.
If the top two hypotheses are equally supported (within 10%), suspect a compound cause -- both may be true simultaneously.
Anti-Patterns
- Anchoring: committing to the first hypothesis that seems plausible
- Confirmation bias: only looking for evidence that supports your preferred hypothesis
- Premature closure: stopping investigation after one piece of supporting evidence
- Ignoring absence: "no errors in this component" doesn't mean the component is innocent
Defense-in-Depth Validation
When you fix a bug caused by invalid data, adding validation at one place feels sufficient. But that single check can be bypassed by different code paths, refactoring, or mocks.
Core principle: Validate at EVERY layer data passes through. Make the bug structurally impossible.
Why Multiple Layers
Single validation: "We fixed the bug." Multiple layers: "We made the bug impossible."
Different layers catch different failure modes:
- Entry validation catches most invalid input
- Business logic catches domain-specific edge cases
- Environment guards prevent context-specific dangers (e.g., destructive operations in test)
- Debug instrumentation captures forensic context when other layers fail
The Four Layers
Layer 1: Entry Point Validation
Reject obviously invalid input at the API/function boundary. This is the first line of defense.
function createProject(string $name, string $workingDirectory): Project
{
if (empty($workingDirectory)) {
throw new \InvalidArgumentException('workingDirectory cannot be empty');
}
if (!is_dir($workingDirectory)) {
throw new \InvalidArgumentException("workingDirectory does not exist: {$workingDirectory}");
}
// ... proceed
}Layer 2: Business Logic Validation
Ensure data makes sense for this specific operation, even if it passed entry validation.
function initializeWorkspace(string $projectDir, string $sessionId): void
{
if (empty($projectDir)) {
throw new \RuntimeException('projectDir required for workspace initialization');
}
// ... proceed
}Layer 3: Environment Guards
Prevent dangerous operations in specific contexts (test, staging, CI).
async def git_init(directory: str) -> None:
if os.environ.get("NODE_ENV") == "test":
normalized = os.path.realpath(directory)
tmp_dir = os.path.realpath(tempfile.gettempdir())
if not normalized.startswith(tmp_dir):
raise RuntimeError(
f"Refusing git init outside temp dir during tests: {directory}"
)
# ... proceedLayer 4: Debug Instrumentation
Capture context for forensics when the other layers fail.
async function gitInit(directory: string) {
const stack = new Error().stack;
console.error('About to git init', { directory, cwd: process.cwd(), stack });
// ... proceed
}Use console.error() in tests (not logger, which may be suppressed). Log BEFORE the dangerous operation, not after it fails. Include context: cwd, env vars, timestamps, stack trace.
Applying the Pattern
When you fix a bug:
1. Trace the data flow -- where does the bad value originate? Where is it consumed? 2. Map all checkpoints -- list every function/boundary data passes through 3. Add validation at each layer -- entry, business logic, environment, instrumentation 4. Test each layer independently -- bypass layer 1, verify layer 2 catches it
Key Insight
All four layers are typically necessary. During testing, each layer catches bugs the others miss:
- Different code paths bypass entry validation
- Mocks bypass business logic checks
- Edge cases on different platforms need environment guards
- Debug logging identifies structural misuse patterns
Don't stop at one validation point.
Root Cause Tracing
Bugs often manifest deep in the call stack. The instinct is to fix where the error appears, but that treats a symptom.
Core principle: Trace backward through the call chain until you find the original trigger, then fix at the source.
When to Use
- Error happens deep in execution (not at entry point)
- Stack trace shows long call chain
- Unclear where invalid data originated
- Need to identify which test/caller triggers the problem
The Tracing Process
1. Observe the symptom
Error: git init failed in <repo-root>/packages/core2. Find the immediate cause
What code directly triggers this?
await execFileAsync('git', ['init'], { cwd: projectDir });3. Trace callers upward
WorktreeManager.createSessionWorktree(projectDir, sessionId)
<- Session.initializeWorkspace()
<- Session.create()
<- test at Project.create()4. Track the bad value
At each level, ask: what value was passed, and where did it come from?
projectDir = ''(empty string)- Empty string as
cwdresolves toprocess.cwd() - That's the source code directory, not the temp dir
5. Find the original trigger
Where did the empty string originate?
const context = setupCoreTest(); // Returns { tempDir: '' }
Project.create('name', context.tempDir); // Accessed before beforeEach ran!Root cause: top-level variable initialization accessing a value that isn't set until beforeEach.
Adding Stack Traces for Instrumentation
When manual tracing hits a dead end, add instrumentation:
async function gitInit(directory: string) {
console.error('DEBUG git init:', {
directory,
cwd: process.cwd(),
nodeEnv: process.env.NODE_ENV,
stack: new Error().stack,
});
await execFileAsync('git', ['init'], { cwd: directory });
}Use console.error() in tests (not logger -- may be suppressed). Log BEFORE the operation, not after failure. Capture and filter:
npm test 2>&1 | grep 'DEBUG git init'Finding Test Pollution
When a test passes in isolation but fails in the suite, another test is polluting shared state.
Bisection approach: Run tests one at a time until the polluter is found.
# Run each test file individually, check if artifact appears after each
for f in $(find src -name '*.test.ts'); do
npx jest "$f" --forceExit 2>/dev/null
if [ -d ".git/worktrees/phantom" ]; then
echo "POLLUTER: $f"
break
fi
doneAnalyze stack traces from instrumentation to find the pattern (same test? same parameter? same setup function?).
Key Principle
Never fix just where the error appears. Trace back to find the original trigger. After finding the source, also add defense-in-depth validation at each layer the data passes through (see defense-in-depth.md).
Specialized Debugging Patterns
Environment Diagnostics
Before investigating, capture the environment state using collect-diagnostics.sh:
bash collect-diagnostics.sh # print to stdout
bash collect-diagnostics.sh diag.md # write to fileCollects system info, language versions, git state, project files, and environment variables. Use during differential analysis to compare working vs broken environments, or attach to bug reports.
Intermittent Issues
- Track with correlation IDs across distributed components
- Race conditions: look for shared mutable state, check-then-act patterns, missing locks. In async code (Node.js, Python asyncio): interleaved
.then()chains, unguarded shared state between concurrent tasks, missing transaction isolation in DB operations - Deadlocks: check for circular lock acquisition (DB row locks held across multiple queries), circular
awaitdependencies in async code, connection pool exhaustion blocking queries that would release other connections - Resource exhaustion: monitor memory growth, connection pool depletion, file descriptor leaks. Under load: check pool size vs concurrent request count, verify connections are returned on error paths (finally/dispose)
- Timing-dependent: replace arbitrary
sleep()with condition-based polling -- wait for the actual state, not a duration
CI Failures
When a CI check fails on a PR or branch:
1. Fetch logs: gh run view <run_id> --log (extract run ID from the checks URL). If detailsUrl points to a non-GitHub provider (Buildkite, CircleCI), don't attempt to fetch logs -- report the URL and let the user investigate. 2. Classify the failure: build error (compilation/dependency), test failure (which test, what assertion), lint/type error (which rule, which file), timeout (which step exceeded limits), or infrastructure (runner OOM, network, flaky service). 3. Reproduce locally: run the same command from the CI config (cat .github/workflows/*.yml to find it). If it passes locally, the issue is environment-specific -- compare CI runner config against local (OS, versions, env vars, caching). 4. Fix and verify: fix the issue, then suggest re-running the relevant checks: gh pr checks <pr> --watch or gh run rerun <run_id> --failed.
Don't retry a CI run without changing something. If the same run failed twice, it's not flaky -- it's broken.
Postmortem
After resolving non-trivial bugs, document a lightweight postmortem:
1. Timeline: when introduced, when detected, when resolved (include commit SHAs) 2. Root cause: one sentence -- the actual cause, not the symptom 3. Impact: what broke, for how long, who was affected 4. Fix: what changed and why this fix addresses the root cause 5. Prevention: what test, monitor, or process change prevents recurrence
Common Bug Patterns
- Async ordering -- missing
await, unhandled promise rejection, callback firing before setup completes. The temporal gap between setup and callback is where bugs hide. - Stale state -- cached values, stale closures, outdated config, old build artifacts. When behavior contradicts the code you're reading, verify you're running what you think you're running.
- Stale build artifacts -- a test failure whose source path is provably correct and untouched by your diff is the tell: the source on disk is right, but an incremental build relinked a stale object. A clean working tree (
git status) does not mean a clean build tree -- build outputs are typically gitignored. Baseline the build, not the commit: rebuild from clean (make clean, freshtarget/) before debugging the code. Checking out an old commit inherits the same stale objects and proves nothing. - Recurring fix site -- if
git logshows 3+ prior fixes in the same file, the file needs redesign, not another patch. Escalate as architectural smell.
First Move by Bug Class
The first debugging move depends on the bug class. "Add logging" is the default reflex, but for some classes it is the wrong first move -- it captures nothing and burns a cycle.
- Visual / rendering / layout -- read statically first. Instrumentation cannot capture what the compositor, layout engine, or cascade actually did. Read the render path and inspect computed styles (resolved values, not the source rule) instead of logging. A log fires before paint and says nothing about the rendered result.
- Behavioral / lifecycle / async / state -- instrument first, before writing any fix. Add the probe (a log or assertion) as part of forming the hypothesis, not after a fix has already failed. These bugs live in values and ordering that are invisible from a static read; the probe is how the hypothesis becomes observable.
- Pure logic (off-by-one, wrong branch, bad comparison) -- a careful static read is sufficient. No instrumentation needed; the defect is on the page once the path is read end to end.
Write the question before the log. Before adding any probe, state the yes/no question it answers and pre-commit the decision rule: "if this prints X before Y, hypothesis A survives; if not, A is dead." A log with no question attached is noise -- it produces output, not evidence.
A log that changes the behavior is itself evidence. If adding or removing a probe makes the bug appear, disappear, or move, that signals a timing, lifecycle, or concurrency defect -- the observation is perturbing the very ordering that is broken. Do not chase the now-hidden symptom; treat the sensitivity as the lead and investigate the race.
Bug Triage
When multiple bugs exist, prioritize by:
- Severity (data loss > crash > wrong output > cosmetic) separately from Priority (blocking release > customer-facing > internal)
- Reproducibility: always > sometimes > once. "Sometimes" bugs need instrumentation before fixing.
- Quick wins: if a fix is < 5 minutes and unblocks others, do it first
Signals You're Off Track
Watch for these signs from the user -- they indicate you've left the systematic process:
- "Is that not happening?" -- you assumed behavior without checking
- "Will it show us...?" -- you're not gathering enough evidence
- "Stop guessing" -- you're proposing fixes without root cause
- "We're going in circles" -- same hypothesis repackaged, not a new approach
- Repeating the same type of fix with slight variations -- that's not a new hypothesis
#!/usr/bin/env bash
# collect-diagnostics.sh — Gather environment diagnostics for debugging
# Usage: bash collect-diagnostics.sh [output-file]
#
# Collects system info, language versions, git state, and project metadata.
# Outputs structured report to stdout or optional file.
set -euo pipefail
OUTPUT="${1:-}"
collect() {
local buf=""
buf+="# Diagnostic Report"$'\n'
buf+="**Collected:** $(date -u +%Y-%m-%dT%H:%M:%SZ)"$'\n\n'
# --- System ---
buf+="## System"$'\n\n'
buf+="| Property | Value |"$'\n'
buf+="|----------|-------|"$'\n'
buf+="| OS | $(uname -s) $(uname -r) |"$'\n'
buf+="| Arch | $(uname -m) |"$'\n'
buf+="| Shell | ${SHELL:-unknown} |"$'\n'
if command -v bash &>/dev/null; then
buf+="| Bash | $(bash --version | head -1) |"$'\n'
fi
buf+="| User | $(whoami) |"$'\n'
buf+="| PWD | $(pwd) |"$'\n'
buf+=$'\n'
# --- Disk / Memory ---
buf+="## Resources"$'\n\n'
buf+='```'$'\n'
buf+="Disk (pwd): $(df -h . 2>/dev/null | tail -1 | awk '{print $4 " available of " $2}')"$'\n'
if command -v free &>/dev/null; then
buf+="Memory: $(free -h 2>/dev/null | awk '/^Mem:/{print $7 " available of " $2}')"$'\n'
fi
buf+='```'$'\n\n'
# --- Git ---
if git rev-parse --is-inside-work-tree &>/dev/null; then
buf+="## Git"$'\n\n'
buf+="| Property | Value |"$'\n'
buf+="|----------|-------|"$'\n'
buf+="| Branch | $(git branch --show-current 2>/dev/null || echo 'detached') |"$'\n'
buf+="| Last commit | $(git log -1 --format='%h %s' 2>/dev/null || echo 'none') |"$'\n'
buf+="| Dirty files | $(git status --porcelain 2>/dev/null | wc -l | tr -d ' ') |"$'\n'
buf+="| Remote | $(git remote get-url origin 2>/dev/null || echo 'none') |"$'\n'
buf+=$'\n'
fi
# --- Language Versions ---
buf+="## Languages & Runtimes"$'\n\n'
buf+="| Tool | Version |"$'\n'
buf+="|------|---------|"$'\n'
for cmd in node python python3 php ruby go java rustc; do
if command -v "$cmd" &>/dev/null; then
local ver
case "$cmd" in
node) ver=$("$cmd" --version 2>/dev/null) ;;
python|python3) ver=$("$cmd" --version 2>/dev/null) ;;
php) ver=$("$cmd" --version 2>/dev/null | head -1) ;;
ruby) ver=$("$cmd" --version 2>/dev/null) ;;
go) ver=$("$cmd" version 2>/dev/null) ;;
java) ver=$("$cmd" -version 2>&1 | head -1) ;;
rustc) ver=$("$cmd" --version 2>/dev/null) ;;
*) ver="installed" ;;
esac
buf+="| ${cmd} | ${ver} |"$'\n'
fi
done
buf+=$'\n'
# --- Package Managers ---
buf+="## Package Managers"$'\n\n'
buf+="| Tool | Version |"$'\n'
buf+="|------|---------|"$'\n'
for cmd in npm pnpm yarn bun pip uv composer cargo bundler gem; do
if command -v "$cmd" &>/dev/null; then
local ver
ver=$("$cmd" --version 2>/dev/null | head -1) || ver="installed"
buf+="| ${cmd} | ${ver} |"$'\n'
fi
done
buf+=$'\n'
# --- Project Detection ---
buf+="## Project Files Detected"$'\n\n'
for f in package.json composer.json pyproject.toml Cargo.toml Gemfile go.mod build.gradle pom.xml Makefile Dockerfile docker-compose.yml .env.example; do
if [ -f "$f" ]; then
buf+="- \`${f}\`"$'\n'
fi
done
buf+=$'\n'
# --- Environment Variables (safe subset) ---
buf+="## Environment (safe subset)"$'\n\n'
buf+="| Variable | Value |"$'\n'
buf+="|----------|-------|"$'\n'
for var in NODE_ENV APP_ENV RAILS_ENV FLASK_ENV ENVIRONMENT CI TERM; do
local val="${!var:-}"
if [ -n "$val" ]; then
buf+="| ${var} | ${val} |"$'\n'
fi
done
buf+=$'\n'
echo "$buf"
}
report=$(collect)
if [ -n "$OUTPUT" ]; then
echo "$report" > "$OUTPUT"
echo "Diagnostics written to ${OUTPUT}"
else
echo "$report"
fi
ia-debugging Specification
Intent
ia-debugging is a discipline-class skill (an engineering practice not tied to one stack). Systematic root-cause debugging with verification. Use when debugging, troubleshooting, or facing errors, stack traces, broken tests, flaky tests, or regressions. For validating bug reports before fixing, use bug-reproduction-validator agent.
Scope
In scope:
- Behaviors described in
SKILL.mdand routed via the should_trigger phrasings indistillery/tests/fixtures/triggers/ia-debugging.jsonl. - Updates to runtime behavior, structure, trigger precision, references, and validation.
Out of scope:
- Acting as the runtime instructions themselves (those live in
SKILL.md). - Trigger phrasings already covered by adjacent
ia-*skills (validate-pluginflags >70% description overlap as DUPLICATE_TRIGGER). - <!-- to fill in: domain-specific exclusions when the skill drifts -->
Trigger Context
- Class:
discipline - Hook regex:
plugins/whetstone/hooks/skill-patterns.sh->SKILL_PATTERNS[ia-debugging] - Common requests (from fixture should_trigger):
- "help me debug this failing test"
- "fix the bug in the login flow"
- "why is this function failing"
- Should not trigger for (from fixture should_not_trigger):
- "write a new React component for the sidebar"
- "plan the implementation of the new API"
- "refactor the user service to use dependency injection"
Source And Evidence Model
Authoritative sources:
SKILL.md-- runtime instructions and reference routing.references/*.md-- bundled supplementary content (4 file(s)).distillery/tests/fixtures/triggers/ia-debugging.jsonl-- positive and negative trigger phrasings under regression test.plugins/whetstone/hooks/skill-patterns.sh-- regex pattern that fires this skill.distillery/.eval-data/ia-debugging/-- harvested session examples (when present).
Data that must not be stored in this skill or its references:
- Secrets, credentials, tokens.
- Machine-specific filesystem paths (
/home/...,/Users/...,~/ai/...). The validator (MACHINE_PATH_LEAK) flags these as HIGH. - Private URLs, customer data, or unredacted personal information.
Coverage matrix
| Dimension | Status | Evidence |
|---|---|---|
| Trigger fixtures | complete | distillery/tests/fixtures/triggers/ia-debugging.jsonl (>=5 should_trigger, >=5 should_not_trigger) |
| Hook regex pattern | complete | plugins/whetstone/hooks/skill-patterns.sh (SKILL_PATTERNS[ia-debugging]) |
| Reference architecture | complete | 4 file(s) under references/ |
| Real-usage signal | <!-- populated by harvest-sessions when sessions exist --> | distillery/.eval-data/ia-debugging/ (created by harvest-sessions) |
Evaluation
Lightweight (run on every change):
python3 distillery/scripts/distiller.py validate-plugin --component ia-debugging
python3 distillery/scripts/distiller.py test-triggers --skill ia-debuggingDeeper (when behavior risk warrants):
python3 distillery/scripts/distiller.py dspy-eval ia-debugging
python3 distillery/scripts/distiller.py diagnose-negatives ia-debuggingAcceptance gates:
validate-plugin --component ia-debuggingreturns 0 HIGH findings.test-triggers --skill ia-debuggingreturns F1 = 1.0 with floors of 5 should_trigger and 5 should_not_trigger.- For dspy-eval, the composite score does not regress against the most recent saved baseline (see
distillery/.eval-data/ia-debugging/history.json).
Known Limitations
<!-- to fill in over time as drift surfaces. Default rule: any time diagnose-negatives surfaces a recurring failure pattern, document it here so future maintainers understand the trade-off the current implementation accepts. -->
Maintenance Notes
- Update
SKILL.mdwhen the runtime workflow, branch conditions, or output contract changes. - Update this
SPEC.mdwhen intent, scope, evidence model, evaluation gates, or maintenance expectations change. - Update the trigger fixture when adding new positive phrasings, removing stale ones, or expanding scope (the 5/5 floor is a hard validator gate).
- Update the hook regex in
skill-patterns.shwhenever fixture positives expose a missed phrasing; verify F1 = 1.0 witheval-triggersbefore committing. - Run the full release pipeline via
/release-- never bump versions or update CHANGELOG.md from a per-skill edit.