debugging-difficult-bugs — instrument, reproduce, read, then fix
Core idea: when you can't see the failure by reading code, make the runtime
tell you. Add temporary append-only JSONL logging along the real code path,
reproduce the real issue once, read the log chronologically, and only then fix.
Never make a second speculative fix without new runtime evidence.
Announce at start: "Using the debugging-difficult-bugs skill — instrumenting
the runtime path to observe the failure."
Step 1: State the uncertainty
Write down: what you believe, what you can't verify statically, and the exact
runtime path that must be observed (route → service → query, edge function, job).
Step 2: Add temporary unconditional instrumentation
Rules:
- Unconditional — never gated behind an env var, debug flag, or log level.
If reproduction requires remembering to set a flag, it will silently not fire.
- Append-only JSONL, one JSON object per line, to a file in the process's
working directory.
- Log boundaries and decisions, not every line: function entry/exit, branch
decisions with the data that caused them, state before/after mutation, async
ordering markers, caught errors, return-value shapes.
- Log shapes, not payloads: ids, keys, counts, statuses. Never log tokens,
auth headers, cookies, or full user content.
import { appendFileSync } from "node:fs";
import { join } from "node:path";
function debugBug(event: string, data: Record<string, unknown> = {}) {
appendFileSync(
join(process.cwd(), "debug-difficult-bug.jsonl"),
`${JSON.stringify({ ts: new Date().toISOString(), event, ...data })}\n`
);
}
debugBug("service.beforeUpdate", { id, companyId, status: row.status });
Carbon multi-process note. The ERP/MES dev servers, edge functions (Docker
edge-runtime container), and Inngest handlers run as separate processes with
different working directories. Log process.cwd() + a process role once at
startup, or use distinct filenames (debug-erp.jsonl, debug-edge.jsonl). For
edge functions, console.error JSON lines (visible in container logs) can stand
in when the container filesystem is awkward to reach.
Step 3: Reproduce the real issue once
- Prefer reproducing yourself: boot the stack (
crbn up if not already
running), authenticate with /auth, and drive the exact failing flow with
agent-browser (the /test skill documents Carbon's form gotchas —
requestSubmit, react-aria blur).
- If only the user can reproduce (their data, their environment), tell them
exactly: "I added temporary logging. Reproduce the issue once, then point me
at
<cwd>/debug-difficult-bug.jsonl."
Step 4: Read the log BEFORE fixing
Read chronologically and answer, in writing:
- Did the instrumented path actually run?
- What was the expected sequence of events?
- What was the actual sequence?
- What is the first point where state/order/branch diverges from expectation?
That first divergence is the root cause candidate. Feed it back into the
root-cause brief (or write one now) — then implement via /fix, whose failing
regression test must assert the actual divergence you observed, not your
earlier assumption.
Step 5: Clean up — mandatory
- Remove every temporary log call, helper, and import.
- Delete generated
.jsonl files.
- Check the final diff explicitly for leftovers:
git diff | grep -n "debugBug\|debug-difficult\|\.jsonl" → expect no hits.
The final diff contains only the fix and its tests.
Done when
1---2name: debugging-difficult-bugs3description: Runtime-instrumentation debugging for bugs that static reading can't pin down — add temporary unconditional JSONL logging to the real code path, reproduce, read the log, then fix. Use when /root-cause lands at MEDIUM/LOW confidence, when a bug involves runtime state, ordering, caching, streaming, concurrency, or manual/UI reproduction, or before a second speculative fix. Skip when a stack trace or a deterministic failing test already proves the cause.4---56# debugging-difficult-bugs — instrument, reproduce, read, then fix78Core idea: when you can't see the failure by reading code, **make the runtime9tell you**. Add temporary append-only JSONL logging along the real code path,10reproduce the real issue once, read the log chronologically, and only then fix.11Never make a second speculative fix without new runtime evidence.1213**Announce at start:** "Using the debugging-difficult-bugs skill — instrumenting14the runtime path to observe the failure."1516## Step 1: State the uncertainty1718Write down: what you believe, what you can't verify statically, and the exact19runtime path that must be observed (route → service → query, edge function, job).2021## Step 2: Add temporary unconditional instrumentation2223Rules:2425- **Unconditional** — never gated behind an env var, debug flag, or log level.26 If reproduction requires remembering to set a flag, it will silently not fire.27- **Append-only JSONL**, one JSON object per line, to a file in the process's28 working directory.29- Log **boundaries and decisions**, not every line: function entry/exit, branch30 decisions with the data that caused them, state before/after mutation, async31 ordering markers, caught errors, return-value shapes.32- Log **shapes, not payloads**: ids, keys, counts, statuses. Never log tokens,33 auth headers, cookies, or full user content.3435```ts36import { appendFileSync } from "node:fs";37import { join } from "node:path";3839function debugBug(event: string, data: Record<string, unknown> = {}) {40 appendFileSync(41 join(process.cwd(), "debug-difficult-bug.jsonl"),42 `${JSON.stringify({ ts: new Date().toISOString(), event, ...data })}\n`43 );44}4546debugBug("service.beforeUpdate", { id, companyId, status: row.status });47```4849**Carbon multi-process note.** The ERP/MES dev servers, edge functions (Docker50`edge-runtime` container), and Inngest handlers run as separate processes with51different working directories. Log `process.cwd()` + a process role once at52startup, or use distinct filenames (`debug-erp.jsonl`, `debug-edge.jsonl`). For53edge functions, `console.error` JSON lines (visible in container logs) can stand54in when the container filesystem is awkward to reach.5556## Step 3: Reproduce the real issue once5758- Prefer reproducing yourself: boot the stack (`crbn up` if not already59 running), authenticate with `/auth`, and drive the exact failing flow with60 `agent-browser` (the `/test` skill documents Carbon's form gotchas —61 `requestSubmit`, react-aria blur).62- If only the user can reproduce (their data, their environment), tell them63 exactly: "I added temporary logging. Reproduce the issue once, then point me64 at `<cwd>/debug-difficult-bug.jsonl`."6566## Step 4: Read the log BEFORE fixing6768Read chronologically and answer, in writing:69701. Did the instrumented path actually run?712. What was the expected sequence of events?723. What was the actual sequence?734. What is the **first** point where state/order/branch diverges from expectation?7475That first divergence is the root cause candidate. Feed it back into the76root-cause brief (or write one now) — then implement via `/fix`, whose failing77regression test must assert the *actual* divergence you observed, not your78earlier assumption.7980## Step 5: Clean up — mandatory8182- Remove every temporary log call, helper, and import.83- Delete generated `.jsonl` files.84- Check the final diff explicitly for leftovers:85 `git diff | grep -n "debugBug\|debug-difficult\|\.jsonl"` → expect no hits.8687The final diff contains only the fix and its tests.8889## Done when9091- [ ] The first divergence point is identified from log evidence (quote the lines)92- [ ] The fix landed via `/fix` with a red→green regression test asserting that behavior93- [ ] Reproduction of the original flow now passes94- [ ] Zero instrumentation remnants in the diff