Failed approaches and reusable mistakes
Things that broke, why, and what not to try again.
Failed approaches and reusable mistakes
Each entry is a thing that actually went wrong in this repo, the root cause, and the constraint it leaves behind. Link here when rejecting a similar idea.
Dual package manager (npm + pnpm) lockfile drift
- What broke: Cloudflare Pages build failed on
feat/landing-page-overhaul(2026-05-02) even though GitHub CI passed. - Root cause: a root
package-lock.jsonand a nested desktoppnpm-lockcoexisted;@saas-maker/eslint-configwas absent from the pnpm lockfile. Pages ranpnpm install --frozen-lockfileagainst a stale lockfile. - Fix: regenerated
pnpm-lock.yaml, deleted the npm lockfile, committed the lockfile in isolation. - Constraint: one package manager (pnpm) across all CI workflows.
Do not reintroduce
package-lock.json. The repo pinspackageManager: pnpm@10.33.2. - See:
PROJECT_STATUS.md2026-07-11 desloppification sweep.
Wrong Cloudflare Pages output directory
- What broke: after the lockfile fix, Pages still produced an empty/wrong build.
- Root cause: Pages
destination_dirwas set todistwhile the desktop Vite config writes toout(apps/desktop/vite.config.tsoutDir: "out"). Pages was also pointed atapps/desktopinstead ofapps/landing-page-astro. - Fix: reconfigure Pages
root_dir: apps/landing-page-astro,destination_dir: dist. The Astro site writes todist; the desktop app writes toout. - Constraint: Pages only ever deploys
apps/landing-page-astro. Never point it atapps/desktop. See operations/landing-deploy.md.
@tauri-apps/plugin-sql was never the DB layer
- What broke: docs claimed
plugin-sqlwas the SQLite layer; it was a dead dependency. - Root cause: the Rust backend has used
rusqliteall along;plugin-sqlwas a leftover from an earlier prototype. - Fix: removed
plugin-sqlin the 2026-07-11 sweep. - Constraint: DB is Rust-internal via
rusqlite. Do not re-addplugin-sql. See architecture/data-model.md.
Claude usage double-counted ~2.2×
- What broke: all Claude token/cost numbers were inflated ~2.2× (measured 103–134% per month).
- Root cause: the indexer summed the
usageobject of every Claude JSONL line, but Claude Code writes one line per content block and each repeats the same final usage — 50%+ of usage lines are byte-identical repeats. - Fix: adapter dedups usage by
(message.id, requestId)with the last key persisted per session (cc_sessions.last_usage_key); one-time backfill re-scans on-disk transcripts. - Constraint: never sum Claude JSONL usage lines without the
(message.id, requestId)dedup. See knowledge/learnings/telemetry-and-indexing.md.
Codex cumulative-token inflation
- What broke: one Codex session booked 61.5B tokens / $35k (true: 391M / ~$220); “today” read ~$12.9k.
- Root cause: Codex reports session-cumulative token totals; the incremental indexer was adding that running total every pass.
- Fix:
tokens_absoluteflag so cumulative tokens are SET not added; one-timefix_codex_token_totalsrepair re-reading each Codex file. - Constraint: Codex tokens are cumulative — use SET semantics, not ADD.
Multi-model Claude sessions billed to the last model
- What broke: a 211MB session with 17k opus-4-7 messages + 1.6k fable-5 messages billed $3.6k entirely to fable.
- Root cause: session-level
model_usedis last-model-wins. - Fix: per-message
session_model_usagetable populated by the indexer + one-time streaming backfill over existing Claude JSONL. - Constraint: by-model attribution must use per-message model splits,
not session-level
model_used.
Indexer burning ~95% of a core
- What broke: sustained background indexer CPU burn.
- Root cause: subagent sidechain transcripts shared the parent’s
sessionId, collapsing onto one DB row so each was re-parsed + archive -replaced every pass; the skip compared drift-prone nanosecond mtime strings. - Fix: skip on exact byte-offset == file-size; key sidechains by unique per-file id; migrate the offset backlog; repair FTS sync UUID handling. Steady-state index pass 87s → 1.9s.
- Constraint: sidechain transcripts need their own key; mtime comparison must be byte-offset-based, not nanosecond-string-based.
tauri-driver native e2e never supported macOS
- What broke: the native e2e path was dead weight.
- Root cause:
tauri-drivernever actually supported macOS in our setup. - Fix: removed in the 2026-07-11 sweep; Playwright chromium against the Vite dev server is the e2e path.
- Constraint: do not reintroduce
tauri-driver. See development/testing.md.
More lessons
See also LESSONS.md for older entries
and DECISIONS.md for the decision
log. When a new failure happens, add an entry here in the same shape
(what broke / root cause / fix / constraint).