Your AI assistant has a memory limit.
When it fills up, the oldest part of your conversation is quietly thrown away.
Nothing warns you. ctx does.
The problem · What it does · Install · Privacy · For geeks · Story
Yes. Every AI assistant can only hold so much of a conversation at once. Claude Code is generous about it, but the limit is real, and when you reach it the software makes room by discarding the earliest messages.
You are not told when this happens. You just notice, later, that it lost the thread — the file you mentioned an hour ago, the decision you made, the constraint you gave it.
It reads notes Claude Code already writes on your own Mac and turns them into one number: how many more exchanges you get before the forgetting starts.
So you can finish the thought, save the important bit, or start fresh on purpose — instead of finding out afterwards.
Once a conversation is long, every single new message re-reads the entire thing. That is what makes long sessions slow and expensive — and it compounds silently.
ctx prints exactly what you avoid by starting fresh at the right moment:
headroom 40 turns
saved by restarting 33.75M tokens
Thirty-three million tokens of pointless re-reading, on one real session. It is your own number, computed from your own files — not a marketing figure.
git clone https://github.com/infinitule/claude-transcript-telemetry.git
cd claude-transcript-telemetry && ./ctxThat's it. No account, no key, no install, no internet. If you have Claude Code and a Mac or Linux box with Python, it works.
./ctx --watch # keep it open in a corner, refreshes itself
./ctx --all # every session you've hadIt never sends anything anywhere and never writes to anything. It opens files in your own ~/.claude folder, counts numbers, and prints them. It reads counts and timestamps — not what you or the AI actually said.
The innovation, why nothing else does this, and the caveats.
The trap · Context depth · Why it's new · Data layout · Tailing · Cost · Honesty table
Claude Code writes its session transcript to ~/.claude/projects/<encoded-cwd>/<session>.jsonl, appended live. Every reader hits the same trap:
It emits one JSONL line per content block. A single API response — one message.id — spans several type: "assistant" lines that each repeat the same message.usage. Sum the lines and you count the same tokens three times.
| Measured on a real 2-hour session | |
|---|---|
assistant lines carrying usage |
821 |
unique message.id values |
327 |
| inflation factor | 2.51× |
| naive line-summed cost estimate | $1,312.66 |
| deduplicated estimate | $390.11 |
| authoritative harness figure | ~$463.84 |
The fix is three lines:
if d.get("type") != "assistant": continue # skip non-assistant lines
u = (d.get("message") or {}).get("usage")
by_id[d["message"].get("id") or synthetic()] = u # last usage wins per idThe genuinely new part. cache_read_input_tokens is the conversation prefix served from cache; add this turn's cache_creation_input_tokens and you have the working context size:
depth ≈ cache_read_input_tokens + cache_creation_input_tokens
ctx regresses depth over the last 20 turns for growth-per-turn, then divides the remaining window to get turns of headroom — amber under 60, red under 20. The window is inferred (depth > 200K ⇒ 1M session, else 200K); override with --limit.
Every existing Claude Code usage tool answers "what has this cost me?" — a backward-looking question about money. Three consequences follow:
- They all read the transcript naively, so their numbers are ~2.5× high unless they replicate the
message.iddedup. - They measure the wrong scarce resource. On a flat-rate plan the money is irrelevant. Context is still finite, still degrading your output, and still invisible.
- Nobody surfaces depth at all. There is no API for it, no status line, no warning. The signal has to be derived from cache-token behaviour, which is why it has been missed.
ctx is forward-looking: not what you spent, but how much room you have left.
| Path | Contents |
|---|---|
~/.claude/projects/<cwd>/<session>.jsonl |
The transcript. Live-appended, tens of MB. |
~/.claude/bash-commands.log |
Timestamped command feed — the live "what is it doing". |
$TMPDIR/harness-cost-<sessionId>.json |
{ts, cost_usd} — authoritative cost. Valid only if ≤ 300 s old. Often absent. |
~/.claude/cost-tracker.log |
Misleadingly named — holds commands, not costs. Do not parse for money. |
- Keep a byte
offset;seek, read to EOF, setoffset = size. - Keep a
carryfor the trailing partial line — without it you eventually parse half a JSON object. size < offsetmeans rotation: reset everything.- Re-scan for the newest transcript; a new session is a new file.
Four rates apply, differing by ~200×, so an undifferentiated token sum is meaningless.
| Model | input | output | cache write | cache read |
|---|---|---|---|---|
| haiku | 0.80 | 4.00 | 1.00 | 0.08 |
| sonnet | 3.00 | 15.00 | 3.75 | 0.30 |
| opus | 15.00 | 75.00 | 18.75 | 1.50 |
USD per million tokens; cache write = 1.25× input, cache read = 0.1× input.
Even deduplicated this ran 15.9% under the authoritative figure, because a flat table can't express the >200K-token and 1-hour-cache 2× tiers. So --cost is opt-in and labelled an estimate; prefer the harness file when fresh.
| Claim | Status |
|---|---|
One line per content block; usage repeats per message.id |
Verified — 821 → 327 |
| 2.51× inflation factor | Measured |
| Deduped estimate 15.9% under authoritative | Measured |
cost-tracker.log holds commands, not costs |
Verified by inspection |
harness-cost-*.json schema + 300 s freshness |
Verified from reference implementation |
depth ≈ cache_read + cache_creation |
Derived — well-grounded proxy, not a documented field |
| 200K / 1M window inference | Heuristic — override with --limit |
| Published per-token rates | Approximate, and they change |
skills/claude-transcript-telemetry/SKILL.md packages all of this for Claude Code itself:
/plugin marketplace add infinitule/claude-transcript-telemetry
/plugin install claude-transcript-telemetry@claude-transcript-telemetryswiftc -O images.swift -o mkimages && ./mkimages # macOSA note from me — edit this freely, it's your repo.
I wasn't looking for this. I was poking at the Touch Bar on my 2018 MacBook Pro, which I'd basically stopped using, wondering what would actually be worth putting on a strip that's always visible. The idea I landed on was a live cost meter for AI work — money burning invisibly while you work seemed like the thing an always-on display should fix.
Building it, the numbers didn't line up. My estimate said $1,312; the harness said $464. A 183% error is not a rounding problem, so I went looking for the cause instead of fudging a constant — and found that Claude Code writes one line per content block, each repeating the same usage record. Naive readers over-count by about 2.5×.
Then the meter itself turned out to be pointless for me: I'm on a flat-rate plan, so the money was never the scarce thing. What is scarce is context. And the same records I'd been parsing contained the signal for it — cache_read_input_tokens is the conversation prefix being re-read every turn, which means it's a direct measure of how full the window is.
So the tool I meant to build got thrown away and the thing underneath it turned out to be more useful than the thing I set out to make. That felt worth packaging up as a skill so nobody else has to find the 2.5× the hard way.
MIT — see LICENSE. Not affiliated with Anthropic. "Claude" and "Claude Code" are trademarks of Anthropic PBC; this project reads files those tools write on your own machine.



