Code you designed or wrote is guilty until proven innocent. Your intent doesn't matter — only the code's actual behavior.
This rule applies symmetrically to defensive code. When the user asks "is this even needed?" or "doesn't the library already do that?", run the underlying library/tool with the bad-input case BEFORE explaining why your code defends against it. Often the library already raises the exact exception you're catching — making your validation dead code. Concrete example: redis-py's RedisCluster(load_balancing_strategy="typo") accepts the bad string at construction and only raises on first read. A wrapper-level enum lookup that "fails loudly at startup" sounds defensible — until empirical test shows the wrapper isn't actually fast-failing earlier than the library would in practice. Run it before defending it.
A. Document Current State
- What is the EXACT error message?
- What are the EXACT reproduction steps?
- What is ACTUAL vs EXPECTED output?
- When did this start?
B. Map the System
- Trace execution path from entry point to failure
- Identify all components involved
- Read relevant source files completely (don't skim)
- Note all dependencies, imports, configurations
C. Gather External Knowledge (when needed)
- Search for exact error messages
- Check library/framework docs for intended behavior
- Look for known issues, breaking changes, version quirks
B. Test Each Hypothesis
For each:
- What would prove this true?
- What would prove this false?
- Design minimal test
- Document results
C. Eliminate or Confirm
Don't proceed until: which hypothesis is supported by evidence? What evidence contradicts others?
- Design — What is the MINIMAL change that addresses the root cause?
- Implement — Make the change with verification logging if needed
- Test — Does the original issue still occur? Run reproduction steps. Check for regressions.
Evidence
[Exact errors, behaviors, outputs observed]
Investigation
[What you checked, found, and ruled out]
Root Cause
[The actual underlying problem + evidence]
Solution
[What you changed and WHY it addresses the root cause]
Verification
[How you confirmed it works and doesn't break anything else]
</output_format>
<language_examples>
### Python — Adding Debug Points
```python
# Temporary debug prints (remove after fixing)
import json
print(f"DEBUG input: {json.dumps(data, indent=2, default=str)}")
print(f"DEBUG result type: {type(result).__name__}, value: {result!r}")
# Using pdb for interactive debugging
import pdb; pdb.set_trace() # drops into debugger at this line
# pytest debugging — run with -s flag to see prints
# pytest -s -x tests/test_problematic.py::test_specific_case
Python — Minimal Reproduction
# Isolate the bug — strip everything non-essential
def test_reproduce_bug():
"""Minimal reproduction of the issue."""
# Arrange: minimum state needed
data = {"key": "value"}
# Act: the problematic operation
result = process(data)
# Assert: what should happen vs what does happen
assert result == expected_value, f"Got {result!r}, expected {expected_value!r}"
Node.js — Debug Points
// console.debug with structured data
console.debug('DEBUG processOrder:', { orderId, items: items.length, total });
// Using Node.js debugger
debugger; // pause here when running with --inspect
// Run specific test with verbose output
// npx jest --testNamePattern "specific test" --verbose --no-coverage
Node.js — Async Debugging
// Common async bug: unhandled rejection
async function debugAsyncIssue() {
try {
const result = await problematicOperation();
console.debug('result:', result);
} catch (err) {
// Log the FULL error including stack
console.error('FULL ERROR:', err);
console.error('Stack:', err.stack);
throw err; // re-throw after logging
}
}
During investigation:
1---2name: debugging3description: Stack-specific debugging (Python/async/pdb, logs, stack traces) when root cause is unclear. superpowers:systematic-debugging owns the core hypothesis loop; escalate isolated hard bugs to the python-debugger subagent.4---56<objective>7Methodical debugging using scientific method: gather evidence, form hypotheses, test systematically, verify fixes. Treats code you wrote with MORE skepticism than unfamiliar code — cognitive bias about "how it should work" is the enemy of debugging.8</objective>910<core_principle>11VERIFY, DON'T ASSUME. Every hypothesis must be tested. Every fix must be validated. No solutions without evidence.1213Code you designed or wrote is guilty until proven innocent. Your intent doesn't matter — only the code's actual behavior.1415**This rule applies symmetrically to defensive code.** When the user asks "is this even needed?" or "doesn't the library already do that?", run the underlying library/tool with the bad-input case BEFORE explaining why your code defends against it. Often the library already raises the exact exception you're catching — making your validation dead code. Concrete example: redis-py's `RedisCluster(load_balancing_strategy="typo")` accepts the bad string at construction and only raises on first read. A wrapper-level enum lookup that "fails loudly at startup" sounds defensible — until empirical test shows the wrapper isn't actually fast-failing earlier than the library would in practice. Run it before defending it.16</core_principle>1718<context_scan>19Run at invocation to detect project type:20```bash21[ -f "package.json" ] && cat package.json | grep '"typescript"' > /dev/null && echo "DETECTED: TypeScript" || ([ -f "package.json" ] && echo "DETECTED: Node.js")22[ -f "Cargo.toml" ] && echo "DETECTED: Rust"23([ -f "pyproject.toml" ] || [ -f "setup.py" ] || [ -f "requirements.txt" ] || find . -maxdepth 2 -name "*.py" | head -1 | grep -q .) && echo "DETECTED: Python"24[ -f "go.mod" ] && echo "DETECTED: Go"25[ -f "pom.xml" ] || [ -f "build.gradle" ] && echo "DETECTED: Java"26(find . -maxdepth 2 -name "*.cpp" -o -name "*.cc" -o -name "CMakeLists.txt" | head -1 | grep -q .) && echo "DETECTED: C++"27```28If domain expertise skills exist (`~/.claude/skills/expertise/`), offer to load them before investigation.29</context_scan>3031<evidence_gathering>32Before proposing any solution:3334**A. Document Current State**35- What is the EXACT error message?36- What are the EXACT reproduction steps?37- What is ACTUAL vs EXPECTED output?38- When did this start?3940**B. Map the System**41- Trace execution path from entry point to failure42- Identify all components involved43- Read relevant source files completely (don't skim)44- Note all dependencies, imports, configurations4546**C. Gather External Knowledge (when needed)**47- Search for exact error messages48- Check library/framework docs for intended behavior49- Look for known issues, breaking changes, version quirks50</evidence_gathering>5152<root_cause_analysis>53**A. Form Hypotheses**54List possible causes with evidence:551. [Hypothesis] — because [specific evidence]562. [Hypothesis] — because [specific evidence]5758**B. Test Each Hypothesis**59For each:60- What would prove this true?61- What would prove this false?62- Design minimal test63- Document results6465**C. Eliminate or Confirm**66Don't proceed until: which hypothesis is supported by evidence? What evidence contradicts others?67</root_cause_analysis>6869<solution_development>70Only after confirming root cause:71721. **Design** — What is the MINIMAL change that addresses the root cause?732. **Implement** — Make the change with verification logging if needed743. **Test** — Does the original issue still occur? Run reproduction steps. Check for regressions.75</solution_development>7677<critical_rules>781. NO DRIVE-BY FIXES: If you can't explain WHY a change works, don't make it792. ONE VARIABLE: Change one thing at a time, verify, then proceed803. COMPLETE READS: Don't skim code. Read entire relevant files.814. CHASE DEPENDENCIES: If libraries/configs/external systems are involved, investigate those too825. QUESTION PREVIOUS WORK: Maybe the earlier "fix" was wrong. Re-examine with fresh eyes. **When a hypothesis is refuted mid-investigation, do NOT immediately propose the next fix.** Re-run evidence-gathering with the refuting observation now in scope. A dead hypothesis often carried a hidden assumption that also poisons the "obvious next thing to try" — the second fix will fail for the same reason unless you re-baseline first.836. DON'T RAISE TO ROUTE: If a code path is unsafe in some contexts (wrong thread, missing dep, wrong env), split the call sites so the unsafe path is unreachable — don't `raise` and lean on a framework retry / failure-list to recover. Raise-as-control-flow is a band-aid: it leaks resources, hides intent, and couples your fix to framework internals. The correct fix routes at the call site (separate helpers, separate phases, separate executors).847. MIRROR CI's ENV BEFORE GREEN: After any dep bump that may drop a transitive (sensi-* version change, redis-py major, etc.), `pip uninstall -y <orphaned-transitive>` (or `pip install -r requirements.txt --force-reinstall` in a fresh venv) BEFORE running pytest. A green local run in a polluted venv that still has the old transitive doesn't prove anything about CI's clean Docker build. The pre-push hook runs against your venv, not the wheel set requirements.txt produces.858. CLEAN STATE BEFORE VERIFYING: Tear down stale containers/volumes/networks/locks before re-running any verification. Don't act-then-think. Common contamination sources: prior `docker compose up` left a partially-formed Redis Cluster `nodes.conf`; previous test run's temp files still on disk; observability query window overlapping with the failed run; local branch behind remote. Before ANY re-run, ask "what state would invalidate this result, and have I cleared it?" If unsure, investigate before running. Trust pushback when a result feels off — contamination is often the cause. When a verification "fails" but the harness output looks correct (table printed, artifact written, but the system-under-test returned 500s), distinguish a HARNESS bug from a SYSTEM bug — re-running won't fix the latter.869. VERIFY THE LIBRARY ACCEPTS THE VALUE BEFORE REASONING ABOUT IT: When the user (or you) proposes a config knob change — `1/100milliseconds` on a rate limiter, `timeout=0.5s` on a synchronous client, `port=0` for ephemeral assignment — open a REPL and pass the value to the underlying library FIRST. Sub-second granularity, fractional seconds, custom granularity strings, value-of-zero special cases, units the parser doesn't recognize — every library has a value range, and "it sounds reasonable" is not evidence. The cost of one `python -c "from <lib> import parse; print(parse('<value>'))"` is two seconds; the cost of a long reasoned response that turns out to assume a value the parser rejects is the rest of the turn. Same rule applies symmetrically: if the user's intuition is "this should be configurable", verify it actually is before agreeing or disagreeing.8710. INCIDENT STABILIZERS ARE THE SMALLEST REVERSIBLE CHANGE: When prod is on fire, ship the env-var kill switch / feature flag / config flip in PR #1 — minimal lines, no behavior change in the off state, deployable today. File the structural fix (single-flight, lock manager, schema rewrite) as the NEXT ticket. Don't bundle them; you'll either ship the stabilizer slower waiting for the structural review, or ship the structural change without the review rigor it needs. The two changes optimize for different things: the stabilizer optimizes for *time to mitigation*, the structural fix optimizes for *durability*. Conflating them costs you both. Incident kill switches should default to the SAFE state (e.g., for a "limiter disabled" knob during a thundering-herd incident, `enabled=False` is the default — re-enable per env when the underlying race is fixed, don't preserve the broken-in-prod behavior as the default).8811. TWO STRIKES AGAINST A BLACK-BOX = SWITCH MODES: When a fix targets an opaque system (docker daemon, container orchestrator, cloud API, network fabric, third-party service) and two attempts fail without producing a validated hypothesis, STOP retrying. Return to `<evidence_gathering>` — pull logs, `docker inspect`, network traces, or reduce to a minimal reproducer. Trial-and-error against a black-box burns turns and produces false-signal fixes; a change that "makes it green" without an explained mechanism is a coincidence, not a fix. If two retries didn't reveal the cause, the third won't either. Same rule symmetrically for CI: green in CI + red locally against the same commit points to environment state (daemon, cached images, orphaned volumes, DNS), not code — investigate the delta, don't keep re-running.8912. VERIFY THE OBSERVABILITY BACKEND'S ACTUAL FIELD NAMES BEFORE FILTERING: Log-search backends (Groundcover, Datadog, CloudWatch Logs, ELK, Splunk, Loki) each expose different canonical fields for tenant scoping — and different tenants on the same backend often diverge. Before filtering by `env=prod`, `service=X`, `namespace=Y`, `src_cluster=Z`, run one values-probe query first (`| field_values <candidate>` on gcQL; `fields @log | stats count() by <field>` on CloudWatch Insights; `_field_names` / `| label_values` on Loki; `fields.list` on Datadog) and confirm the field exists AND has non-empty values in your window. Filtering on the wrong field silently returns zero results — indistinguishable from "the bug isn't there," which routes you toward wrong hypotheses. Two-second probe beats a two-minute chase. Same shape as rule 9 (verify library accepts a value) but at the schema layer, not the value layer.9013. TWO REJECTIONS OF YOUR EDIT = READ THE GROUND TRUTH: When an Edit/patch to a file YOU wrote (or are mid-change on) is rejected or fails to apply 2+ times, STOP re-guessing the surrounding text and Read the actual file (and the tests that pin its contract) before re-proposing. Repeated rejections mean your mental model has diverged from what's on disk — blind retries re-fail the same way and burn approval cycles. Ground the next edit in the bytes you just read: match real indentation/whitespace, confirm the function/contract still looks as you assumed, then edit once. This is rule 5/11 applied at the edit layer — a refuted edit is evidence your model is stale, not a reason to try a fourth phrasing.9114. SOURCE GROUND TRUTH FROM THE RUNNING ARTIFACT, NOT REPO HEAD: When a deployed/running artifact (a built image, a live container, an applied config, a released package) is the authoritative source of a schema/model/config, read it FROM that artifact — the deployed version can diverge from repo `master`/HEAD. A repo `create_tables.sql`/model/config can be ahead OR behind what's actually running (e.g. an image ships migration ALTERs that repo master already dropped, or a lockfile pins a version the tag doesn't). Extract from the image/container (`docker run --entrypoint cat <image> <path>`, `kubectl exec`, inspect the built wheel) and treat repo HEAD as a fallback, not the truth. Reasoning from repo source about what production runs is the same stale-model trap as rules 5/13, at the artifact layer.92</critical_rules>9394<output_format>95```markdown96## Issue: [Problem Description]9798### Evidence99[Exact errors, behaviors, outputs observed]100101### Investigation102[What you checked, found, and ruled out]103104### Root Cause105[The actual underlying problem + evidence]106107### Solution108[What you changed and WHY it addresses the root cause]109110### Verification111[How you confirmed it works and doesn't break anything else]112```113</output_format>114115<language_examples>116117### Python — Adding Debug Points118```python119# Temporary debug prints (remove after fixing)120import json121print(f"DEBUG input: {json.dumps(data, indent=2, default=str)}")122print(f"DEBUG result type: {type(result).__name__}, value: {result!r}")123124# Using pdb for interactive debugging125import pdb; pdb.set_trace() # drops into debugger at this line126127# pytest debugging — run with -s flag to see prints128# pytest -s -x tests/test_problematic.py::test_specific_case129```130131### Python — Minimal Reproduction132```python133# Isolate the bug — strip everything non-essential134def test_reproduce_bug():135 """Minimal reproduction of the issue."""136 # Arrange: minimum state needed137 data = {"key": "value"}138139 # Act: the problematic operation140 result = process(data)141142 # Assert: what should happen vs what does happen143 assert result == expected_value, f"Got {result!r}, expected {expected_value!r}"144```145146### Node.js — Debug Points147```javascript148// console.debug with structured data149console.debug('DEBUG processOrder:', { orderId, items: items.length, total });150151// Using Node.js debugger152debugger; // pause here when running with --inspect153154// Run specific test with verbose output155// npx jest --testNamePattern "specific test" --verbose --no-coverage156```157158### Node.js — Async Debugging159```javascript160// Common async bug: unhandled rejection161async function debugAsyncIssue() {162 try {163 const result = await problematicOperation();164 console.debug('result:', result);165 } catch (err) {166 // Log the FULL error including stack167 console.error('FULL ERROR:', err);168 console.error('Stack:', err.stack);169 throw err; // re-throw after logging170 }171}172```173</language_examples>174175<success_criteria>176Before starting:177- [ ] Context scan run to detect project type178179During investigation:180- [ ] Root cause identified with evidence (not just "it works now")181- [ ] Fix verified against original reproduction steps182- [ ] Adjacent functionality checked for regressions183- [ ] Can explain the solution to another developer184- [ ] One variable changed at a time (no simultaneous changes)185- [ ] Would pass code review scrutiny186</success_criteria>187188<reference_index>189All in `references/`:190- **debugging-mindset.md** — Cognitive biases, first-principles thinking191- **hypothesis-testing.md** — Forming and testing falsifiable hypotheses192- **investigation-techniques.md** — Binary search, minimal reproduction, rubber duck193- **verification-patterns.md** — What "verified" actually means194</reference_index>