Systematic Debugging
Custom Guide
bash "${CLAUDE_PLUGIN_ROOT}/lib/load-custom-guide.sh" cf-sys-debug
If the block above printed anything, apply only the ## Before, ## Rules, and ## After sections; if it shows the raw command instead of output, re-run that exact load-custom-guide.sh fence now.
Core Constraint
Do not touch code until you can state the root cause in one sentence:
"I believe the root cause is [X] because [evidence]."
Name a specific file, function, and line. Vague labels are not testable. If you cannot be that specific, you do not have a hypothesis yet.
Same symptom after a fix = hard stop. Recurrence or "let me just try this" means the hypothesis is unfinished. Re-read the execution path from scratch before touching code again.
After 3 failed hypotheses, stop. Use the Handoff Format below. Ask how to proceed.
Rationalization Watch
Stop and re-examine when these surface:
| Thought | Rule |
|---|---|
| "I'll just try this one thing" | Write the hypothesis first |
| "I'm confident it's X" | Confidence is not evidence — instrument it |
| "Probably the same issue as before" | Re-read the execution path from scratch |
| "It works on my machine" | Environment difference IS the bug — enumerate every env difference |
| "One more restart should fix it" | Read the last error verbatim. Max two restarts without new evidence |
Progress Signals
Diagnosis is moving when:
- A log line matches the hypothesis → find one more independent piece of evidence
- You can predict the next error → run the prediction
- Cause is in A, symptoms in B → confirm each link in the A→B chain
- You can write a test that would fail on the old code → write it before the fix
Do not claim progress without observable evidence matching at least one signal.
4-Phase Process + Documentation
Phase 1: Root Cause Investigation
1a. Check existing bug docs (memory recall):
Recall memory (Verbs in ${CLAUDE_PLUGIN_ROOT}/context/bootstrap.md) with 2–3 keywords from the bug: { "query": "<bug keywords>", "type": "episode", "limit": 3 }; grep scope {CF_DOCS_ROOT}/memory/bugs/**/*.md.
Read the top 1–2 matches.
1b. Investigate:
- Read the actual error — full stack, message, logs. Do not guess.
- Reproduce with a reliable test or command.
- Trace backward from the error to its origin.
Phase 2: Pattern Analysis
- When did it start?
git log --oneline -20,git diff HEAD~5 - Consistent or intermittent? Intermittent = timing/state.
- Minimal reproduction — strip everything unrelated.
- Deflection — an area someone dismisses is often where the problem lives.
Bisect Mode
Activate for "used to work, now broken" or "broke after an update".
- Anchor
last-known-goodon the most recent good tag:git tag --sort=-version:refname | head -5. Do not use a date or raw SHA. - Define a non-interactive pass/fail command with a clear exit code. Reuse it at every step.
git bisect start,git bisect bad(current),git bisect good <tag>. Let bisect drive.- Do not re-read large files each step — note the key function/line once.
- When bisect names the culprit: read only that commit's diff; identify the introducing line.
Phase 3: Hypothesis Testing
- One hypothesis: "The bug is caused by [X] because [evidence]." Name file and line.
- One instrument: log, assertion, or smallest test that would fail if correct. Run it.
- Evidence contradicts → discard completely. Re-orient. Do not keep a disproved hypothesis.
- External tool/API failure: diagnose first (server, key, config) before switching tools.
- Stack trace in a library? Walk back 3 frames into your code. The bug is almost always there.
Phase 4: Implementation
- Fix the root cause, not the symptom. >5 files → pause and confirm scope.
- Regression Guard — if the bug recurred or was previously "fixed":
- A regression test that fails unfixed and passes fixed
- Lives in the project suite (not a temp file)
- Commit message states why it recurred and why this fix prevents it
- Write a regression test that would have caught this
- Full test suite — no collateral breakage
- Verify the original error is gone
Capturing out-of-scope side-effects
Non-trivial problem unrelated to the root cause → do not fix inline. Record it, then continue:
bash "${CLAUDE_PLUGIN_ROOT}/lib/capture-later.sh" \
--name "<short title>" --description "<what & where — enough to act on cold>" \
--source cf-sys-debug [--slug <bug-doc/task slug, if one exists>] [--problem "<the bug under investigation>"]
Writes <docsDir>/later/YYYY-MM-DD-<name>.md (frontmatter: slug, problem, conversation_id).
Phase 5: Document the Bug
Hard bugs always get a doc.
- Read
language(local.coding-friend/config.jsonoverrides global, defaulten) MAIN_REPO_ROOTfrom SessionStart bootstrap (session-init.sh); elsepwd. Config fromCF_CONFIG_FILE($MAIN_REPO_ROOT/.coding-friend/config.json) fordocsDir(defaultdocs) — do not search sub-folders. Docs base:CF_DOCS_ROOT.- Dispatch
cf-writer(absolutefile_path):
WRITE SPEC
----------
task: create
file_path: {CF_DOCS_ROOT}/memory/bugs/YYYY-MM-DD-{name}.md
language: {language from config}
content: |
---
title: "<Short bug title>"
description: "<One-line summary of the bug and fix, under 100 chars>"
tags: [tag1, tag2, tag3]
created: YYYY-MM-DD
updated: YYYY-MM-DD
type: episode
importance: 4
source: conversation
---
# <Bug Title>
## Overview
<Symptom and why it was hard to diagnose>
## Investigation
<Tried and ruled out>
## Root Cause
<Real cause>
## Fix
<What changed>
## Prevention
<How to avoid it>
## Related Files
- `path/to/file`
readme_update: false
auto_commit: false
existing_file_action: skip
Existing bug files without a date prefix stay as-is — do not rename.
Frontmatter: description is a factual grep summary (not "Hard bug fixed"). tags: error type, module, root-cause category.
Index in CF Memory (MANDATORY)
Required — do not skip. After cf-writer saves the file, call memory_store yourself (the writer does not):
title/description/tagsfrom frontmattertype:episodecontent: full markdown including frontmatterimportance: 4source: "auto-capture"index_only: true
If MCP is unavailable, warn the user — do not fail silently.
2-line summary:
- Markdown file:
{docsDir}/memory/bugs/...md(created or updated) - Memory DB: indexed ✓ — or: MCP unavailable, file only
Gotchas
| What happened | Rule |
|---|---|
| Patched symptom file, not origin | Trace the path backward first |
| Switched tools after MCP/API failure | Diagnose server, key, config first |
| Pipeline "RUNNING" but a stage is bad | Test each stage in isolation |
| Race diagnosed as stale state | Inspect timestamps and ordering before state |
| Local repro, CI fail | Align env (runtime, vars, timezone) before chasing code |
| Stack deep in a library | Walk back 3 frames into your code |
try/catch to hide the error |
Find the root cause |
| Multiple changes at once | One change at a time; test after each |
Debugging Tools
git bisect, git stash, targeted logs, smallest reproduction.
Outcome
Success Format
Root cause: [what was wrong, file:line]
Fix: [what changed, file:line]
Confirmed: [evidence or test that proves the fix]
Tests: [pass/fail count, regression test location]
Regression guard: [test file:line] or [none, reason]
Status: resolved, resolved with caveats (state them), or blocked (what is unknown).
Handoff Format (after 3 failed hypotheses)
Symptom:
[Original error, one sentence]
Hypotheses Tested:
1. [Hypothesis] → [Test] → [Ruled out because...]
2. ...
3. ...
Evidence Collected:
- [Logs / stack / files]
- [Repro steps]
- [Env: versions, config, runtime]
Ruled Out:
- [Eliminated causes]
Unknowns:
- [Still unclear / missing info]
Suggested Next Steps:
1. [Next direction]
2. [Tools or permissions needed]
3. [Context the user should provide]
Status: blocked
Review Reminder
After the fix is verified, ask whether to run /cf-review or /cf-commit. Do not auto-run.