| name | debug |
| description | Use when code behaves differently from expectations — 'why does this happen', 'find the bug', 'follow the callstack', symptom reports even without the word debugging. Blackbox methodology: AS-IS/TO-BE confirmation, callstack tracing recorded in a CWD-relative trace file, red-green fix. |
stv:debug: 'Blackbox Debugging'
A debugging methodology that records everything like an airplane black box while hunting for the root cause.
Core constraint: An unexplored branch is an unexamined branch.
1. Problem Definition — AS-IS / TO-BE Confirmation
Before starting, always confirm with the user:
- Forward: "Did X, got Y → doing X should produce Z"
- Reverse: "Got Y → should have been Z"
The gap between AS-IS and TO-BE is the bug. Do not start debugging without confirmation.
2. Tracing — Follow the Callstack While Recording in trace.md
Create a debugging log file under the current working directory (NOT the home directory):
./.claude/stv/debugging/{issueID}-{YYYYMMDDhhmm}/trace.md
The ./ prefix is intentional — every stv artifact (spec.md, trace.md, debugging trace) lives under the active CWD so multi-tenant / multi-session agents stay isolated. The tradeoff is that the same {issueID} started in different working trees yields separate trace dirs; that is the intended isolation model, not a bug.
Path guards (all three are hard rules):
- NEVER write to the home-level
.claude. The trace dir is always the CWD-relative ./.claude/stv/…; a trace path that resolves to the HOME-level .claude (~/.claude/…, $HOME/.claude/…, /Users/<user>/.claude/…, /home/<user>/.claude/…) is a bug in the run, not a fallback — older versions of this skill polluted ~/.claude/stv/debugging/ exactly this way. (A checkout that happens to live under /Users/<user>/project/ is fine — the guard is about the home .claude, not the home prefix.) If the CWD is somehow unwritable, stop and surface that instead of falling back to home.
- Keep trace dirs out of commits (gitignore). When the CWD is a git checkout,
./.claude/stv/ must be git-ignored before the first trace write: if ! git check-ignore -q .claude/stv; then echo '.claude/stv/' >> .git/info/exclude; fi. Default to .git/info/exclude (local scratch policy, no tracked-file mutation); append to the repo's .gitignore instead only when the project explicitly wants the ignore rule shared. A debugging trace that leaks into a commit is scratch entering the tree.
- Trace dirs are disposable evidence — cleanup is part of closing. When debugging concludes, fold the durable findings (root cause, the red→green test, the fix rationale) into the issue/PR/spec that owns the bug; the trace dir itself has served its purpose. Leave it in place for session-scoped CWDs (the sandbox is thrown away), and delete or trash it in long-lived checkouts once its content has been folded in. The lifecycle rule: durable knowledge moves out, the scratch dir never outlives the investigation.
Follow the callstack one step at a time from the entry point, recording in this file.
Recording Rules
- At each step, specify the actual filename:line number.
- At conditional branches, record which condition leads where.
- API calls to other services, DB queries, and message queue publications are part of the callstack. When crossing service boundaries, follow into that service's code and continue tracing.
- Do not assume. Only write what you have directly read and verified in the code.
Exploration Strategy: Heuristic → Exhaustive
- Phase 1 — Heuristic: Quickly check the top-3 most likely hypotheses first.
- Phase 2 — Exhaustive: If top-3 yields nothing, draw the callstack graph in trace.md and exhaustively explore every branch by actually writing each one down.
3. Verification — Red-Green Cycle
Once a hypothesis is identified:
- Write a test that reproduces the bug first (Red — confirm the test fails)
- Apply the fix
- Confirm the test passes (Green)
- Confirm existing tests are not broken (regression prevention)
- Feed the root cause back to the vertical trace — if the bug lives in a feature that has
docs/{feature}/trace.md, update that trace via the Delta Protocol (a wrong transformation rule → MODIFIED; a missing error path → ADDED) so trace and code stay synchronized. The debugging trace dir is scratch; the vertical trace is the contract.
4. Systematic Debugging Principles
The above blackbox method covers how to trace. These principles cover how to think while debugging.
4a. Reproduce First
- 재현 없이 수정하지 마라. 재현할 수 없으면 계측(instrumentation)부터 추가하라.
- 재현 가능하면 → 가설 수립. 재현 불가면 → 로그/계측 추가 후 재실행.
4b. Single Hypothesis per Iteration
- 한 번에 하나의 가설만 검증한다.
- 여러 수정을 동시에 하면 뭐가 효과인지 모른다.
- 가설이 틀리면 원복하고 새 가설을 세워라.
4c. Root-Cause Tracing
- 증상이 나타난 곳이 아니라 원인이 시작된 곳을 찾아라.
- 콜스택을 역방향으로 추적: "이 값은 어디서 왔나?" → "그걸 누가 넘겼나?" → 원점까지.
- 증상 지점에서 수정하면 다른 경로로 같은 버그가 재발한다.
4d. Condition-Based Waiting (Flaky Test 대응)
setTimeout/sleep 대신 조건 폴링을 써라.
- 패턴:
waitFor(() => condition, timeout) — 10ms 간격 폴링 + 타임아웃 필수.
- 타이밍 테스트가 아닌데 임의 딜레이를 쓰면 flaky의 원흉.
4e. Defense-in-Depth (버그 수정 후)
- 버그를 고친 뒤, 데이터가 지나는 모든 레이어에 검증을 추가하라.
- 4개 레이어: Entry Point → Business Logic → Environment Guard → Debug Instrumentation.
- 한 곳만 막으면 다른 코드 경로로 우회된다. 구조적으로 불가능하게 만들어라.
4f. 3회 실패 시 아키텍처 의심
- 3번 연속 수정이 실패하면 멈추고 구조를 의심하라.
- 각 수정이 다른 곳에서 새 문제를 만들면, 그건 버그가 아니라 설계 문제다.
- 더 고치지 말고 유저/Oracle과 상의하라.
trace.md Example
# Bug Trace: ISSUE-1234 — Soccer team names appear in results
## AS-IS: Movie search results include soccer team names mixed in
## TO-BE: Movie search results should only contain movies
## Phase 1: Heuristic Top-3
### Hypothesis 1: Search query runs without category filter
- `SearchController.cs:45` → calls `SearchService.Query()`
- `SearchService.cs:112` → check categoryFilter parameter → **passed as null** ✅ Likely
### Hypothesis 2: DB table join is incorrect
- `SearchRepository.cs:78` → check SQL → join is correct ❌ Ruled out
### Hypothesis 3: Response mapping mixes in other entities
- `SearchMapper.cs:34` → check mapping logic → correct ❌ Ruled out
## Conclusion: Hypothesis 1 confirmed — categoryFilter passed as null to SearchService.Query()