| name | debug-logs |
| description | Iterative debugging with DEBUG() logs. Use when you can't solve a problem on the first attempt — add targeted logs, read output, narrow the search, repeat until fixed, then clean up. |
Iterative Debug Logging
When you can't solve a problem by reading code alone, use DEBUG() to observe runtime behavior. This is a methodical process — not a one-shot fix.
The DEBUG() Function
Canonical implementation at packages/shared/src/debug.ts. Import from the shared package or the web app re-export:
import { DEBUG } from "@volat/shared";
import { DEBUG } from "@/lib/debug";
Call signatures:
DEBUG('message')
DEBUG('location', 'message')
DEBUG('location', () => ({ data: value }))
DEBUG('location', 'message', () => ({ data: value }))
The data argument is a factory function (lambda) — expensive computation is deferred and only runs when logging is enabled. DEBUG is disabled in test and production (NODE_ENV).
Output goes to /tmp/deep-research-debug/debug-YYYY-MM-DD.log and console (cyan).
The Process
Step 1: Hypothesize
Before adding any logs, form a hypothesis about where the bug likely is. Ask:
- What is the symptom? (e.g., "counter is frozen", "state not updating", "wrong value displayed")
- What code path produces the visible output? Trace backwards from the symptom.
- Which functions sit between the input and the broken output?
Step 2: Add targeted DEBUG() calls
Add 2-4 calls in the functions you suspect. Log the values that would confirm or reject your hypothesis. Be strategic — avoid noisy locations like tight loops unless you suspect the loop itself.
Good placement:
- At the entry of a function you suspect, logging its inputs
- At decision points (if/else branches) to see which path was taken
- Right before and after a value transformation to see what changed
Bad placement:
- Inside a loop that runs hundreds of times per second
- In functions you have no reason to suspect
- Logging entire objects when one field would suffice
Step 3: Reproduce and read
Reproduce the bug. Then read the log:
cat /tmp/deep-research-debug/debug-$(date +%Y-%m-%d).log
If the file is missing or empty:
ls -la /tmp/deep-research-debug/
For live tailing while reproducing:
tail -f /tmp/deep-research-debug/debug-*.log
Step 4: Narrow or widen
If the logs reveal the bug — fix it, go to Step 5.
If the logs show expected behavior in the suspected area — the bug is elsewhere. Widen scope:
- Remove the DEBUG() calls that proved unhelpful
- Add new ones in the next most likely location
- Move further up or down the call chain
If the logs show something unexpected but you can't pinpoint why — add more calls to narrow within the same area. Log intermediate values, branch decisions, loop iterations.
Repeat Steps 2-4 until the root cause is clear.
Step 5: Clean up
After fixing the bug, remove DEBUG() calls that were only useful for this investigation. Keep a call only if:
- It logs a value that would be useful for future debugging of the same area
- It sits at a critical decision point that is hard to reason about from code alone
- It captures state that is otherwise invisible (e.g., values computed across multiple steps)
When in doubt, remove it. The calls are easy to add back.
Step 6: Clear logs
rm -f /tmp/deep-research-debug/*.log
Example
Symptom: "The SSE counter is frozen — client connects but count never updates."
Hypothesis: The broadcast function might not be sending updates.
Add DEBUG() in broadcast() and tick():
function tick(): void {
count += multiplier;
DEBUG("tick", () => ({ count, multiplier }));
broadcast();
}
function broadcast(): void {
const state = getState();
const json = JSON.stringify(state);
DEBUG("broadcast", () => ({ count: state.count, jsonMatch: json === lastJson }));
if (json === lastJson) return;
lastJson = json;
}
Read logs: If you see tick incrementing but broadcast always showing jsonMatch: true, the dedup comparison is wrong — lastJson is being set before the check.
Fix, then remove the DEBUG() calls (or keep one in broadcast if the dedup logic is tricky enough to warrant permanent observability).