2026-08-11 · 13 min read · tikr
tikr: I was lying to myself about how many tokens I burn
Local, encrypted, append-only usage tracking for Claude Code, Codex, and Copilot CLI — and the 2.2x double-count bug.
I run Claude Code a lot. Like, a lot. At some point “I have no idea what this is costing me in equivalent API dollars” stopped being funny.
Cloud dashboards are laggy, incomplete, or not for the tools I actually use. So I built tikr: a local ledger of token usage across Claude Code, Codex, and GitHub Copilot CLI. Nothing is sent anywhere. MIT. Solo.
The problem most trackers get wrong
Claude Code deletes old session transcripts (cleanupPeriodDays, default 30). Any tool that answers “how much have I used?” by reading what’s on disk right now will report a number that falls over time.
Tokens didn’t un-happen. Evidence got pruned.
tikr is an append-only ledger of its own. Read each transcript once from a byte offset, fold into store, file can die afterward. Deleted transcripts never subtract. A write that would lower totals is refused. That’s a guarantee, not a vibe.
How I used Claude Code to build a Claude Code tool
Yes, recursive. The agent that burns tokens helped me measure the burn.
What worked:
1. Measure real transcripts before coding. 212 files, tens of thousands of assistant entries. LESSONS.md has the numbers. 2. Write failing tests from measurements, then implement. 3. Refuse to add providers without verified on-disk formats. Cursor / OpenCode / Gemini CLI were checked and marked untrackable when they don’t store usage locally. Guessing would make pretty wrong charts.
Claude Code is excellent at plumbing (TUI, autostart plists, OTLP receiver). It is dangerous at counting if you let it “just sum the usage fields.”
The 2.2x bug (please tattoo this)
One API message becomes several JSONL entries (one per content block). Each carries a full copy of message.usage.
Naive sum → output tokens inflated ~2.2x. Cache reads ~1.76x. I measured it. It’s not theoretical.
Fix: dedupe by message.id. Within a message, take max per field (streaming partial → final), never sum, never “first entry wins.”
Duplicates were always consecutive in the files I measured — so O(1) lastMessage per file is enough. If Claude Code ever interleaves blocks, that assumption breaks and undercounts. Documented. Re-check on big version jumps.
Encryption without theater
Ledger at ~/.tikr/ledger.jsonl: AES-256-GCM, key from machine identity + per-install salt. Hash-chained. Copy the folder to another machine → decrypt fails. Edit a byte → decrypt fails.
What it does not do: protect you from yourself. Same user can derive the key. That’s honest. I’m not shipping a password prompt that kills the headless daemon.
Product surfaces
tikr start— backfill, daemon, login itemtikr/tikr ui— live TUI, ~0.5s to show a new message (measured ~0.42s)tikr stats— by tool, model, day, project, block, hour heatmaptikr statusline— one line for Claude Code’s own prompt- optional OTLP feed for Claude’s own cost figure + subagent split (never double-count with transcripts)
Five-hour blocks matter because that’s how Claude Code meters. Burn rate shows two numbers: with cache and without. Cache dominates; a single rate is a lie shaped like a statistic.
Cost column is equivalent API list-rate estimate, not your subscription bill. Basis labeled (exact / family / partial). Understating visibly beats overstating invisibly.
Working with the agent
I kept docs/LESSONS.md and plans under docs/plans/. When Claude Code “fixed” ingest and totals jumped, the first question was always: did we reintroduce double-count?
Regression tests with (bug) labels. Green tests that assert the wrong shape are how silent zero-count ships. I have been burned. The README literally warns contributors: verify against real transcripts.
Open source
github.com/mubashirjamali101/tikr
curl -fsSL https://raw.githubusercontent.com/mubashirjamali101/tikr/main/install.sh | bash
tikr start
tikr statsI built tikr because my gut was a bad accountant. Claude Code helped me ship the plumbing fast. The counting rules came from sitting with real JSONL like an adult. If your AI usage tool doesn’t talk about dedupe and retention, it’s probably storytelling with charts.
