debug
Systematic bug diagnosis — reproduce, isolate, hypothesize, verify, fix. Usage: /debug [what's broken]
SKILL.md.claude/skills/debug/SKILL.md
GitHubSkill: /debug
Systematic Bug Diagnosis
PROTOCOL
Bugs have two parts: the symptom (what you observe) and the cause (what's actually wrong). The protocol moves from symptom → cause → fix, one verified step at a time.
Phase 1 — Reproduce
Goal: Reliable, minimal reproduction before touching any code.
- State the symptom precisely: what input, what output, what was expected.
- Find the smallest input that triggers it.
- Verify you can reproduce it consistently.
- Is this a regression?
git log --oneline -20— when did it last work?
If you can't reproduce it: Stop. Gather more information. Unreproducible bugs are unsolvable.
Phase 2 — Isolate
Goal: Narrow the search space to the smallest possible area.
- Identify the layers involved: UI → API → service → DB → external?
- Test each boundary — where does correct input produce wrong output?
- Binary-search the call stack: disable half, does the bug disappear?
- Is it environment-specific? dev vs prod? one machine vs all?
Isolation heuristics:
- Works in tests, breaks live → check environment (config, secrets, timing)
- Started after a deploy →
git bisect - Intermittent → look for shared mutable state, races, time-dependent logic
Phase 3 — Hypothesize
State one falsifiable hypothesis before changing any code:
Hypothesis : [what I think is wrong]
Evidence for: [what supports this]
Against : [what doesn't fit]
Test : [one action that confirms or refutes]
One hypothesis at a time. Testing multiple simultaneously makes it impossible to know what fixed it.
Phase 4 — Verify
- Run the test that confirms or refutes.
- Confirmed → move to fix.
- Refuted → form a new hypothesis. Don't modify the failing thing yet.
- Keep a short log: what you tried, what you learned.
Phase 5 — Fix
- Make the minimal change that fixes the root cause — not the symptom.
- Run the full test suite.
- Add a regression test that would have caught this bug.
- Commit message explains why, not just what:
fix: [what was broken] Root cause: [why it happened] Fix: [what changed and why this is correct]
COMMON BUG PATTERNS
| Pattern | Symptom | Where to look |
|---|---|---|
| Off-by-one | Wrong last/first element | Loop bounds, slice indices, pagination |
| Nil/null dereference | Crash on access | Unguarded pointer use, missing null checks |
| Race condition | Intermittent, timing-dependent | Shared state, goroutines/threads, async code |
| Wrong input assumptions | Works in tests, breaks in prod | Input validation, edge cases, empty/max values |
| Config/env mismatch | Works locally, breaks in CI/prod | .env files, env var names, defaults |
| Stale state | Shows old data | Caches, memoization, DB transaction isolation |
| Type coercion | Wrong math, unexpected falsy | JS ==, int/float truncation, string/int mixing |
| Dependency version | Broke after upgrade | Changelog, breaking changes, peer dependencies |
REGRESSION TEST FORMAT
// Regression: [short description of what was broken]
// Arrange: exact conditions that triggered the bug
// Act: the action that was broken
// Assert: the correct outcome
The test must reproduce the exact failing scenario, not a simplified analogue.
RULES
- Reproduce before touching code. A fix without reproduction is a guess.
- One change at a time. Multiple simultaneous changes make causation unknowable.
- Fix the root cause, not the symptom. Wrapping a bug in an
ifis not a fix. - Always add a regression test. If it broke once, it can break again.
git bisectfor regressions. Faster than reading the diff.- Tag unverified claims
[hypothesis]when communicating status.
PROJECT QUICK REFERENCE
Stack: Go [inferred — no go.mod yet; project in planning stage as of 2026-06-25]
Test commands
# Run all
go test ./...
# Run all with race detector (preferred)
go test -race ./...
# Run single package
go test ./internal/mine/...
# Run single test by name
go test ./internal/mine/... -run TestChunkFilter
# Coverage
go test -cover ./...
# Verbose output (shows each test name)
go test -v ./...
Debug logging
No debug env vars in source yet [project pre-implementation]. When adding:
- Use
os.Getenv("SESSION_INDEXER_DEBUG")or similar for verbose output - Ollama probe failures already log to stderr:
warn: ollama unavailable, indexed without embeddings - SQLite errors surface via returned
error— checkerr != nilafter every DB call
To manually inspect the SQLite DB:
# Open index
sqlite3 .claude/sessions.db
# Check schema version
SELECT * FROM meta;
# Count chunks and pending embeddings
SELECT COUNT(*) FROM chunks;
SELECT COUNT(*) FROM chunks WHERE id NOT IN (SELECT chunk_id FROM embeddings);
# WAL checkpoint status
PRAGMA wal_checkpoint;
Known fragile areas
Derived from architecture docs — no git history yet:
| Area | File (planned) | Risk |
|---|---|---|
| JSONL parsing | internal/mine/parse.go |
Binary heuristic for tool_result (len>10KB or base64 pattern); tool block 2KB truncation edge cases |
| Noise filter | internal/mine/chunk.go |
Strips chunks <30 chars after strip — easy to over-filter multilingual content |
| Dedup logic | internal/mine/mine.go |
INSERT OR IGNORE on (session_id, message_index, chunk_index) — if session_id is absent from JSONL, falls back to filename stem; mismatch = duplicates |
| Ollama probe | internal/embed/embed.go |
2s timeout on GET /api/tags; bge-m3:latest model-name match is exact string — version suffix in tag list will fail silently |
| Float32 BLOB | internal/embed/embed.go |
encoding/binary LittleEndian; corrupted BLOB = cosine NaN; check len(blob) % 4 == 0 and len(blob) == 4096 |
| Schema version | internal/db/db.go |
Hard exit on mismatch — if user has old DB, they must run reindex; no migration path |
| FTS5 sync | internal/db/schema.sql |
Triggers keep FTS5 in sync; if trigger fires after content delete without FTS delete → phantom results |
| Search fallback | internal/search/search.go |
Cosine over all embeddings loaded into memory — at 10k+ chunks this is ~40MB; no pagination |
Stack-specific debug notes
Pure Go / no CGO:
modernc.org/sqlite— no systemlibsqlite3needed; if build fails with sqlite errors, checkgo.modreplaces or proxy issues- Race detector (
-race) is safe to run — no CGO exclusions needed
Ollama connectivity:
# Is Ollama running?
curl -s http://localhost:11434/api/tags | jq '.models[].name'
# Is bge-m3 available?
curl -s http://localhost:11434/api/tags | jq '[.models[].name] | map(select(startswith("bge-m3")))'
# Manual embed test
curl -s -X POST http://localhost:11434/api/embed \
-d '{"model":"bge-m3:latest","input":"test"}' | jq '.embeddings[0] | length'
# Expected: 1024
SQLite WAL:
# If reads appear stale after write
PRAGMA wal_checkpoint(FULL);
# Check WAL file size (>10MB = checkpoint not running)
ls -lh .claude/sessions.db-wal
Stop hook timing:
- Hook timeout = 60s;
minemust complete within it - To time a real mine run:
time session-indexer mine <jsonl> --db .claude/sessions.db - If Ollama is slow, embedding 100 chunks can hit ~30s; test with
--skip-embedflag if added