I gave Claude Code read-only access to its own spending

Four board-formed concrete blocks in a row, each roughly twice the height of the one before it, the tallest capped with a glowing orange bar against a dark void and a thin orange horizon line

I typed a question into Claude Code the other week and it answered with my own billing data.

Not from an API. Not from a dashboard sitting in another window. It read the transcripts in ~/.claude/projects/, ran them through the same cost engine my menu bar app uses, and told me what this project had cost over the last seven days, broken down by model. Mostly Opus, and most of that on a single Tuesday afternoon.

Then it offered to open the session.

I shipped Claudoscope 1.0 on July 27, 2026, four and a half months after the first version put a single number in my menu bar on March 15. That loop, an agent answering questions about itself from local data, is the part of the release I didn’t plan and now use more than the dashboard. The other part is cost alerts, which exist for the opposite reason. Some things you should never have to ask about at all.

Here’s the Thing The dashboard answers the questions you go looking for. These two answer the ones you don’t: nine read-only MCP tools so you can query your Claude Code spend from the terminal you’re already in, and four cost-alert rules that fire at a threshold and again at each doubling, before the bill instead of after.

Where should the answer live?

Next to the question, which isn’t always the same place twice. A dashboard is the right surface for anything you sit down to ask: which project drifted this quarter, or what the config linter makes of a CLAUDE.md you just rewrote. You open it because you already have the question in hand.

The rest of it doesn’t work that way. A session going sideways is worth knowing about while it’s going sideways. A credential in a transcript matters most in the minutes after it lands, not at month end. Claude sitting on a permission prompt while I read something in another window is pure dead time, and I found it three minutes late every single time.

Those questions rarely get asked, because asking one means you already suspected the answer. So 1.0 went into two surfaces that don’t wait to be visited: one you can query from the terminal you’re already typing in, and one that interrupts you.

What can Claude Code actually query about itself?

Nine read-only tools, off by default. They cover usage totals, project and session search, config linting, saved plans, and canon records. All of them run inside the app and answer from transcripts already on your disk, so nothing leaves the machine and no API call is involved.

In practice it means you type things like this into a normal session:

what did I spend on this project last week, broken down by model
find the session where I set up the deploy pipeline
which of my projects has the worst cache hit rate

That last one is the query I didn’t expect to care about. Cache hit rate is the difference between paying $0.30 and $3.00 per million input tokens on Sonnet, and it’s invisible unless something is tracking it across sessions.

Questions I couldn’t ask before

What does my config cost me? Three tools compose into an answer. lint_config estimates the token budget of your instruction files, get_config reports which of them load and at which scope, and get_usage gives session counts per project. Instructions are a tax you pay on every message rather than once, so a 4,000-token CLAUDE.md spread across a few hundred sessions stops being a rounding error. I learned that the slow way when I built the linter. The difference now is that I don’t have to do the multiplication myself, and I can ask it per project instead of in the aggregate.

Is my agent routing actually paying for itself? The premise behind sending a “where is this defined” lookup to a cheap model and a design-sensitive refactor to an expensive one is economic, and until now it was an assumption I had never tested against my own data. list_sessions takes an include_subagents flag, and get_session returns subagent linkage alongside a per-model breakdown, so you can ask which roles genuinely cost less than doing the work inline. Some of mine didn’t. A cheap model that needs three attempts is more expensive than one good pass, and that shows up in the per-model numbers long before it shows up in your intuition.

Why it reuses the dashboard’s engine

The usage tools call the same analytics code the dashboard calls, rather than a reimplementation or a trimmed-down version for the tool layer.

This sounds like an implementation detail and it’s the whole thing. Two code paths computing the same number will diverge. Not immediately, but the first time you patch a pricing table in one place and not the other. Then the dashboard says $41, the agent says $38, you trust neither, and the feature is worse than never having shipped. One engine or don’t bother.

What guards the transcripts

This project exists because I once found my database password sitting in a Claude Code session file. Handing an agent a pipe into those same files needed some care.

DecisionWhy
Read-only, alwaysNo tool writes, deletes, or transitions anything. A buggy write tool against your session history is unrecoverable.
0600 unix socketA file on disk with owner-only permissions rather than a network port, so no other account on the machine can reach it.
Secrets masked in every responseSame scanner the app uses. If it would flag it in the UI, it never reaches the model context.
Registration through the claude CLIEnabling it shells out to the CLI instead of editing ~/.claude.json behind your back.
Off by defaultIt’s a pipe into your transcripts. That should be a decision you made on purpose.

The masking is the one worth arguing about. It’s tempting to skip, because you’re feeding data to a local agent that already has filesystem access, so what exactly are you protecting?

Tool output goes into a transcript. That transcript becomes another file on disk. Which is the precise problem the app was built to detect. Unmasked secrets in MCP output would mean Claudoscope generating the thing it warns you about.

Cost alerts, before the bill instead of after

Optional and off by default. Four rules, with thresholds you set in estimated dollars or tokens, delivered as a macOS notification plus a red dot on the menu bar icon that persists until dismissed.

RuleWhat it catches
Single-session capThe one session that got away from you
Rolling window, 5 min to 4 hrRunaway burn. A loop re-reading the same context forty times shows up here and nowhere else.
Daily totalDrift. Days that felt normal and weren’t.
Monthly totalThe number you actually get billed against

Why alerts re-fire at each doubling

Alerts fire at the threshold, then at 2x, 4x, 8x.

Firing once isn’t enough, because you dismiss the first banner during a meeting and never learn the session went on to cost six times the threshold. Firing continuously is worse. A notification stream that goes off every thirty seconds gets muted inside a day, and a muted alert is a disabled alert that still looks enabled in your settings screen.

Doubling roughly tracks how the cost feels. The jump from $5 to $10 is worth an interruption; $10 to $11 isn’t.

What the alerts measure against

Rolling spend runs off an in-memory delta ledger that rebaselines whenever the app rescans or you switch pricing providers. That sounds like plumbing, but it’s what keeps the alerts honest. Without it, closing your laptop on Friday and opening it Monday puts a week of spend inside what the app believes is the last four hours, and you wake up to alerts about money you already spent. Alerts also stay quiet until the first reconcile finishes on launch, for the same reason.

Thresholds take about a week to get right, and the useful move is to set them from your own numbers instead of a round figure. Ask the MCP server what your median session costs and what your top decile costs, then put the single-session cap somewhere above the second one. Set the rolling window to the length of your longest normal working block. Anything shorter fires during ordinary deep work, and an alert that goes off during ordinary work is one you’ll have switched off by Thursday.

Two bugs that made the cost numbers wrong

Alerts are only worth having if the figures underneath them are right, so 1.0 got reconciled against real Anthropic bills. A couple of things fell out of that, and both matter if you parse these transcripts yourself.

Web search was not being billed at all. The fee is a cent per search. The documented field is usage.server_tool_use.web_search_requests, and in Claude Code transcripts it is always zero. The real count lives in toolUseResult.searchCount on the tool-result record. Parse the documented field, as I did, and you silently bill nothing. It’s now counted per search, deduped by record, and attributed to the day and model that issued it.

That’s the second undocumented transcript gotcha I’ve hit. The first was intermediate records with a null stop_reason, which double-count your tokens if you sum naively.

Model family detection was backwards. The old logic parsed a version number out of the model id and treated anything unparseable as an older generation.

Which inverts on you every time Anthropic ships something. A new model is unrecognized on day one, so it gets guessed at, and guessed toward the legacy rate. The logic was structurally biased to misprice exactly the models you’re most likely to be running.

1.0 flips it. There’s now an explicit closed list of the generations that actually billed at older rates, and anything outside that list prices at the current rate. A model id the app has never seen is far more likely to be new than ancient.

What this setup does not do

These are estimates. Everything is computed locally from transcripts. It’s reconciled against real bills and it lands close, but it’s a model of your spend rather than the invoice itself, and alerts inherit whatever error that model carries.

Asking about your spend costs you spend. Querying the MCP server runs through Claude Code, which bills tokens, which lands in next week’s numbers. It’s a small amount and it’s genuinely funny to watch the observer turn up in the observed.

Nine tool definitions cost context. They load into any session where the server is enabled. Worth turning off in projects where you’ll never ask.

Read-only means read-only. The server will tell you a session is burning cache but it won’t do anything about it. That’s deliberate for now, though it’s the constraint I go back and forth on most.

macOS 14 or later, Apple Silicon. SwiftUI, native, no plans to change. Same tradeoff as always.

Frequently asked questions

Can Claude Code query my own usage and costs?

Yes, through a local MCP server. Claudoscope 1.0 runs nine read-only tools inside the app, off by default, covering usage totals, project and session search, config linting, plans, and canon records. Queries hit transcripts already on your disk over a 0600 unix socket, with secrets masked in every response.

How accurate are the cost estimates?

They’re estimates reconciled against real Anthropic bills. Two fixes in 1.0 closed most of the gap: web search now bills at a cent per search read from toolUseResult.searchCount, and unrecognized model ids price at current rather than legacy rates. Figures remain local estimates, not invoices.

Will cost alerts spam me?

No. They’re off by default and re-fire at each doubling rather than continuously, so you get one at the threshold, one at 2x, one at 4x. Rolling-window alerts stay suppressed until the first reconcile completes on launch, so reopening a laptop after a week doesn’t replay old spend as new alerts.

Can the MCP server change anything?

No. All nine tools are read-only. Nothing writes, deletes, or modifies session files, config, or canon records. Enabling the server registers the shim through the claude CLI rather than editing ~/.claude.json directly, and it stays off until you turn it on.

Does any of my session data leave the machine?

No. Claudoscope reads local files and computes locally, with zero telemetry. The MCP server listens on a unix socket with 0600 permissions rather than a network port, so it isn’t reachable from another machine or another user account.

Install

Free, MIT licensed, macOS 14 or later on Apple Silicon. Zero telemetry, and it never sends your session data anywhere, because it never sends anything anywhere.

brew tap cordwainersmith/claudoscope
brew install --cask claudoscope

Or take the DMG from the 1.0.0 release. Full release notes are on the Claudoscope blog, covering the SQLite index that cut launch on a 3,000-session corpus from 25 seconds to sub-second, the Files tab with per-edit diffs, and the rest of 1.0.

Closing

Both features come out of something I keep relearning. Computing the right answer is maybe a third of the work. The rest is getting it to show up somewhere you’ll actually be, while it can still change what you do.

A cost figure you read on Friday is trivia. The same figure while the session is running is a decision, and I’d rather have fewer of the first kind.

If you’re already deep in Claude Code’s config surface, the hooks post covers the half nobody uses, and broken Ghostty tab titles is a much smaller version of the same visibility problem. For the org-scale version of this, where the question stops being “what did I spend” and becomes “what is everyone spending”, there’s the hard part of a self-service agent platform.