{"slug": "your-llm-usage-data-is-already-on-your-disk-your-limit-isnt", "title": "Your LLM Usage Data Is Already on Your Disk. Your Limit Isn’t.", "summary": "A developer created an open-source tool called tokenmeter to track LLM token usage locally from client logs, addressing the gap between exact per-session consumption data and the opaque plan limits enforced by API servers. The tool provides detailed session reports with token counts, cost estimates, and cache savings, but cannot read server-side plan ceilings, forcing any percentage-of-plan display to remain an estimate.", "body_md": "*How to Build an Honest Local Token Meter — and the One-Number Trick That Makes an Estimate Worth Trusting*\n\nIf you run agentic coding tools — Claude Code, Cursor, Aider, or anything else that loops a model over your repository — you have probably hit the wall. Not a warning. A wall. You are deep in a refactor, the model finally has the shape of the problem loaded, and your plan limit lands mid-task.\n\nThe wall is not the interesting part. The interesting part is that you never saw it coming, and afterward you still don’t know what caused it.\n\nMost tools expose some version of a usage command that reports a percentage and a reset time. That number is authoritative — it comes from the server that enforces the cap — and it answers exactly one question, once, when you remember to ask. It does not tell you *what* consumed the budget. Not which project, not which day, not which prompt.\n\nThat gap is worth closing, and closing it turns out to be a nice little exercise in measurement engineering: what you can know exactly, what you can only estimate, and what to do about the difference. I’ll use a small open-source tool I wrote, [tokenmeter](https://github.com/plumbgoat/tokenmeter), as the worked example — it’s MIT-licensed and stdlib-only, so it’s easy to read along. But almost none of what follows is specific to it, or to Claude Code. The pattern applies anywhere you’re metering a metered resource you don’t control.\n\n```\n╭─ tokenmeter · detailed session report ─────────────────│ 6 prompts · 52 model turns · 28 tool calls · 09:14–11:05 (111m)││ TOTALS ────────────────────────────────────────│ tokens 3.4M│ est. cost $4.71│ input 33K · output 47K · cache-read 3.0M · cache-write 305K│ cache reuse 90% (higher = cheaper; reads are ~0.1x price)│ peak context 92K (9% of 1.0M window)│ cache savings $13.02 (73% off - reads saved $13.40, writes +$0.38)│ burn rate $2.55/hr this session││ TOP PROMPTS BY COST ─────────── (avg $0.79/prompt)│ 1. $2.60 2.4M tok · 28K out · 24t · 12 tools 09:41│ \"refactor the auth middleware to use the new token store\"│ 2. $1.05 712K tok · 9K out · 15t · 8 tools 09:15│ \"why is the login flow throwing a 500 on expired sessions?\"│ 3. $0.44 190K tok · 3K out · 6t · 4 tools 10:22│ \"add a regression test for the expiry case\"│ …and 3 more prompts totalling $0.62││ PLAN USAGE ─────────── (ESTIMATE - /usage is authoritative)│ last 5 hours $18.90│ vs 5h ceiling 38% of $50.00 █████·········╰─ prompts ranked by cost, not raw tokens: cache reads grow every turn, so late prompts look 'bigger' by token count even when cheap.\n```\n\nStart with the good news, because it’s better than most people expect.\n\nClients that talk to LLM APIs generally log what the API told them. Claude Code writes every session to a JSONL transcript under ~/.claude/projects/, and each assistant message carries the usage object the server returned [1]:\n\n```\n{  \"input_tokens\": 412,  \"output_tokens\": 1834,  \"cache_read_input_tokens\": 58201,  \"cache_creation_input_tokens\": 3092}\n```\n\nPlus a model identifier and a timestamp. That isn’t a reconstruction or a tokenizer estimate. It’s what the server said it charged, recorded by the client, sitting in a file you own.\n\nSo the per-session accounting is arithmetic. Parse the transcript, sum the fields, group by whatever you care about. This part is exact, and it’s exact for a reason worth internalizing: you are not measuring the model, you are reading a receipt.\n\nI assumed that was the whole project. It was maybe a fifth of it.\n\nHere’s the constraint that reshapes everything.\n\nI could compute precisely how many tokens were consumed in the last five hours. But no local file says *“and your ceiling is X.”* The server enforces the cap and does not write it down anywhere you can read it.\n\nThat’s a hard wall, not an insufficiently-clever-search wall. And it forces a conclusion that any tool in this space has to confront: the moment you convert consumption into “percent of your plan,” you are estimating. Mine, [ccusage](https://github.com/ryoppippi/ccusage), anything else. There’s no honest way around it.\n\nYou can hide that. Print a confident percentage and let the reader infer authority you don’t have. I’d argue you shouldn’t, and not for purely ethical reasons — it’s also just bad engineering. A tool that quietly disagrees with the authoritative source teaches users it’s broken. A tool that says “I’m an estimate, and here’s the number the server will actually enforce” stays useful precisely because it never competes with the oracle.\n\nSo the limitation goes in the README, in the tool’s own output, and in the docs:\n\nThe dollar figures are anestimate, not a bill. The authoritative source is the provider’s own usage command.\n\nNaming that boundary cost me the ability to make a strong claim. It bought the thing that turned out to matter more, which I’ll get to in a moment — because the fix only becomes visible once you’ve admitted the problem.\n\nAn early wrong turn: I tried to build the whole thing in tokens.\n\nIt doesn’t work, and it’s worth being precise about why. A million Haiku tokens and a million Opus tokens are not the same quantity of anything you care about. Neither are a million cache reads and a million output tokens — cached input reads run roughly a tenth the price of fresh input. Any metric that adds these together is adding incommensurable units and will mislead you in proportion to how mixed your workload is.\n\nSo normalize. I priced each category with published per-token rates and used dollars as the common unit [2]. In my implementation that’s Opus at $5/$25 per million input/output, Sonnet at $3/$15, Haiku at $1/$5, with cache reads at roughly 0.1× input and cache writes at 1.25×.\n\nBe careful what you claim that number is. On a subscription you don’t pay per token, so the dollar figure isn’t a bill. It’s an API-equivalent proxy — a synthetic common currency that makes Opus, Haiku, and cache reads comparable on a single axis. It answers “how much of my budget did this consume,” not “what do I owe.”\n\nOne deliberate asymmetry: unknown model identifiers get priced as the most expensive family. If I can’t identify a model, I’d rather overstate consumption than let a newly released model slip through undercounted and silently sail past a threshold.\n\nThis is the part that generalizes furthest beyond my little tool.\n\nAn estimate needs a ceiling before it can be a percentage. I shipped defaults, but they’re placeholders — subscription tiers differ by more than an order of magnitude, and nothing local tells me which one you’re on. A placeholder ceiling makes the percentage meaningless.\n\nThen the reframe: I don’t need to know your ceiling. I need one reading of the ratio.\n\nYou run the authoritative usage command. It says 45% for the week. The tool already knows its own estimate for that same window — say $180 in proxy dollars. So:\n\n```\nceiling = est / (pct / 100.0)    # 180 / 0.45 = 400\n```\n\nOne number from the user, and the ceiling is back-computed *in the tool’s own estimation units*.\n\nSit with why that works, because it’s the whole trick. It does not matter that my pricing table is a proxy, or that the estimate carries systematic error. Both sides of that division are denominated in the same distorted currency, so the distortion cancels. The user isn’t telling the tool their plan. They’re telling it how wrong it is, once — and it corrects itself.\n\nThis is just single-point calibration of a proxy metric against a sparse oracle, and it shows up all over applied ML: you have a cheap signal you can compute continuously, an expensive ground truth you can sample rarely, and a monotonic relationship between them. You don’t need the expensive signal often. You need it *once*, to fix the constant.\n\nThe design consequence is that the irreducibly manual step — a human reading one number off an authoritative source — shrinks to a single sentence, said once. Everything downstream gets better for free. When you can’t eliminate a manual step, make it as small and as rare as possible, then build the rest of the system to exploit it.\n\nA bug that taught me something general.\n\nI ranked prompts by total token count. The output was wrong in a specific, suspicious way: the last prompt was always the most expensive. Every session. Even when the last prompt was “thanks, ship it.”\n\nCache reads. In a cached-context agent loop, every turn re-reads the accumulated context, so cumulative token counts grow monotonically through a session *regardless of whether that turn did any work*. Ranking by tokens doesn’t rank prompts by effort. It ranks them by how late they happened.\n\nThe fix is to rank by marginal cost — dominated by output and cache writes, the quantities that only rise when the model actually did something. Now “refactor the auth middleware” outranks “thanks, ship it,” which was the entire point of the feature.\n\nThe general lesson: in any system with cached or accumulated state, cumulative counters are a proxy for elapsed time, not for work. Attribute on the margin, not the total. And put the reasoning in the output where users can interrogate it — a ranking that can’t defend itself is a ranking nobody trusts.\n\nBefore publishing, I installed the tool clean, the way a stranger would.\n\nFirst prompt of a fresh session, it announced: ⚡ 168% of your 5-hour window.\n\nOf course it did. The default ceiling was a placeholder I’d told nobody about, and real usage sailed past it. Every new user’s first interaction would have been a loud, confident, entirely fabricated alarm.\n\nThat isn’t a threshold bug, it’s a category error. I was raising alarms against a number that was never a claim about anything. Take that seriously and the fix follows: if a value is a placeholder, it cannot raise an alarm. Uncalibrated means no threshold alerts at all — one gentle nudge explaining how to calibrate, and silence otherwise. Calibration is also tracked per-window, so calibrating the weekly ceiling doesn’t start firing alerts off a still-default hourly one.\n\nThe lesson isn’t “test before shipping.” I’d tested plenty — on the one machine where I’d long since configured everything correctly by hand. Your own machine is the environment least likely to reveal first-run bugs, because it’s the only one where you’ve already silently fixed them all.\n\nThe warning in tokenmeter runs on every prompt submission. That’s a nervous place for your code: slow means you made the tool slow, and throwing means you broke someone’s session.\n\nTwo rules, both non-negotiable for anything on a hot path.\n\nFail silent. Any exception exits zero. A broken meter is a minor annoyance; a meter that blocks work is uninstalled in anger.\n\n```\nif __name__ == \"__main__\":    try:        main()    except Exception:        sys.exit(0)    # never break the session\n```\n\nNever re-read what hasn’t changed. Naively, each invocation re-parses every transcript in every project — unbounded work that grows forever. A per-file cache keyed on (mtime, size), bucketing usage into hourly bins, means files untouched inside the window are skipped on a stat alone. In practice only the actively-written transcript is re-parsed.\n\nStrip out the specifics and four things survive, and they apply to any metered resource you don’t control:\n\nThe tool still can’t beat the authoritative usage command on authority, and it was never going to — that number lives on a server I can’t see. What it does is answer the question the oracle can’t: *what* is consuming the budget. Which project, which day, which session, which prompt.\n\nThat’s usually the question you actually had.\n\n[1] Anthropic, “Claude Code documentation.” [Online]. Available: [https://code.claude.com/docs](https://code.claude.com/docs)\n\n[2] Anthropic, “Pricing.” [Online]. Available: [https://www.anthropic.com/pricing](https://www.anthropic.com/pricing)\n\n[3] ryoppippi, “ccusage: Usage analysis for Claude Code.” [Online]. Available: [https://github.com/ryoppippi/ccusage](https://github.com/ryoppippi/ccusage)\n\n[4] “tokenmeter: Token and plan-usage reports for Claude Code.” [Online]. Available: [https://github.com/plumbgoat/tokenmeter](https://github.com/plumbgoat/tokenmeter)\n\n[Your LLM Usage Data Is Already on Your Disk. Your Limit Isn’t.](https://blog.devgenius.io/your-llm-usage-data-is-already-on-your-disk-your-limit-isnt-8b341a8d3b27) was originally published in [Dev Genius](https://blog.devgenius.io) on Medium, where people are continuing the conversation by highlighting and responding to this story.", "url": "https://wpnews.pro/news/your-llm-usage-data-is-already-on-your-disk-your-limit-isnt", "canonical_source": "https://blog.devgenius.io/your-llm-usage-data-is-already-on-your-disk-your-limit-isnt-8b341a8d3b27?source=rss----4e2c1156667e---4", "published_at": "2026-07-17 11:49:38+00:00", "updated_at": "2026-07-17 11:56:01.458391+00:00", "lang": "en", "topics": ["developer-tools", "large-language-models", "ai-tools"], "entities": ["tokenmeter", "Claude Code", "Cursor", "Aider", "ccusage"], "alternates": {"html": "https://wpnews.pro/news/your-llm-usage-data-is-already-on-your-disk-your-limit-isnt", "markdown": "https://wpnews.pro/news/your-llm-usage-data-is-already-on-your-disk-your-limit-isnt.md", "text": "https://wpnews.pro/news/your-llm-usage-data-is-already-on-your-disk-your-limit-isnt.txt", "jsonld": "https://wpnews.pro/news/your-llm-usage-data-is-already-on-your-disk-your-limit-isnt.jsonld"}}