Fixing a bug in clocked-in
Your instinct under pressure is to jump to a fix. Resist it. The most expensive mistake is treating a symptom — a plausible patch that hides the real defect so it resurfaces or silently records wrong data. Reproduce it, understand the mechanism, and prove the fix with a test before you commit. The fix is usually the easy part.
This repo is small and self-contained (a Bun CLI, bun:sqlite, no services), so
the loop is short — but the discipline is the same.
The loop
0. Get specifics
What's the exact symptom, and where? Which agent (claude-code / codex / grok /
cursor / opencode / pi)? Which command (report, the TUI, install, a hook)?
Expected vs actual. A concrete number that's wrong ("shows 10h, should be ~1h")
or a copied error beats "it's broken."
1. Reproduce — before touching code
Get to certainty first. Most bugs here reduce to a pure-function repro:
construct the Event[] and call pairIntervals / toolIntervals / unionMs /
computeStats / resolveEvent directly. If it's an end-to-end symptom, drive
the real bins against a throwaway DB (CLOCKED_IN_DB=/tmp/x.db bun dist/hook-cli.js …
then bun dist/cli.js report) — see the running-tests skill. Remember the
committed-bundle gotcha: rebuild dist/ (bun run build) before any e2e check.
Stay skeptical of the reported cause. "opencode isn't recording" might be a missing plugin, a wrong session id, a DB-path mismatch, or a stdin hang — confirm which.
2. Root-cause — state the mechanism in one sentence
You should be able to say exactly why: "OUR_CMD matched any command containing
--agent grok, so uninstall deleted the user's own hook," or "the wait counts
each concurrent agent separately because it sums intervals instead of unioning
them." If you can't articulate the mechanism, keep digging — don't patch.
Where bugs live in this repo:
- Pairing / math (
src/events.ts,src/stats.ts) — off-by-one, wrong key, overlap/union edge cases, cutoff filtering, action mapping. - Hook input (
src/hook.ts) — a harness field named differently (snake/camel/conversation_id), precedence, transcript parsing. - Installers (
src/agents.ts) — the ownership marker matching too much/little, a per-agent config shape, the generated plugin strings. - DB (
src/db.ts) — column migration, null↔undefined mapping. - The bins — forgetting to rebuild
dist/, or the hot path importing something heavy/slow.
3. Write a failing test — red before green
Lock the bug down with a regression test that fails now, passes after. Prefer
the pure level (src/events.test.ts, src/stats.test.ts, src/hook.test.ts) —
it's where the logic lives and the test stays fast and durable. For installer
bugs use a temp HOME (src/agents.test.ts). Run it and watch it fail — a
test written after the fix that only agrees with it proves nothing. If a bug
genuinely can't be a test (a packaging/path issue), say so and state how you'll
otherwise verify.
4. Fix at the right layer — minimally (ponytail)
Smallest correct change that addresses the mechanism, in the layer that owns
it: pairing bugs in events.ts, not patched in the report; input-shape bugs in
resolveEvent, not in each installer. Non-negotiables for this repo:
- A hook must never disrupt the agent —
runHookswallows all errors, the hook bins alwaysprocess.exit(0), and stdin reads must not block. Don't add a throw or an unbounded await on the hook path. - Keep the hot path lean —
clocked-in-hookmust not gain commander/Ink/resvg imports; that's what keeps it ~10 ms. - Prefer a named constant/union over a second hardcoded string when the mechanism is a drifted literal — and repoint every call site.
- Don't smuggle a refactor into a bug fix.
Well-specified fix? Route the typing to another model family via multi-model-orchestration (grok/codex) and keep the diagnosis + verification yourself; a subtle fix is worth racing two lanes.
5. Verify
- The regression test now passes, and genuinely failed before (flip it if unsure).
- The original symptom is gone (re-run the repro).
task checkis green (tsgo + oxlint + oxfmt + tests).- Rebuild
dist/and re-check any e2e path — the installed bins run the bundle, notsrc/. Commit the rebuiltdist/. - No collateral damage — run the full suite, not just the file you touched.
6. Clean up and review
Run /ponytail-review to catch a fix that over-reached, then
/adversarial-code-review for a model-diverse pass that ends in verified
fixes. A bug fix reviewed only by the model that wrote it is rubber-stamped. Fix
what survives.
Anti-patterns to refuse
- Editing code before the bug is reproduced — you can't know you fixed it.
- Accepting the reporter's theory without verifying the mechanism.
- Patching the symptom (swallow the error, clamp the number, add a retry) when the defect is upstream.
- Committing with no failing-then-passing test and no articulated mechanism.
- Adding a throw, an unbounded stdin await, or a heavy import to the hook path.
- Fixing an e2e symptom without rebuilding
dist/— you're testing stale code. - Correcting one copy of a duplicated literal instead of extracting a shared constant and repointing every call site.
- Letting the fix balloon into an unscoped refactor, or leaving the code messier.
- Calling it done with
task checkred or the repro still present.