Marmot — Catch token waste while you can still act
A local Claude Code usage tool that tells you what is wasting tokens—and what to do next.
Status: alpha License: MIT Join the Discord
Marmot reads the session records Claude Code already keeps on your machine and points out where tokens are being wasted: context that has outlived the task, MCP tools that are loaded but never called, poor cache reuse, repeated tool failures, or a costly model doing light work.
The useful part is timing. Marmot can nudge you at the end of a turn, while starting fresh, compacting, or changing course can still save the next one.
Requires Node.js 18 or newer.
npm install -g github:DrDroidLab/marmot marmot init --hooks
Restart Claude Code, then verify it:
marmot doctor
Want to look first? marmot --demo uses synthetic data and reads none of your
sessions.
The desktop notification stays short. Claude Code gets the evidence and the next action in its transcript. Marmot speaks once per rule instead of repeating the same warning after every turn.
Both nudges and the daily digest arrive as a dialog: it carries the marmot, has room for what to do about it as well as what it cost, and stays until you dismiss it. A banner you were not looking at is a banner you missed, which is the whole failure this is here to fix.
marmot test-notification # see a nudge marmot test-notification --digest # see the daily digest marmot test-notification --banner # see the other shape
The two are set separately, so you can keep the interruption for the thing that is costing you money and let the once-a-day summary stay out of the way:
marmot config set notify.style.digest=banner # summary as a banner marmot config set notify.style.nudge=auto # dialog only near a limit marmot config set notify.style=banner # both, as banners
alert |
A dialog. Marmot icon, room for the action, waits for a click. Default for both. |
banner |
The ordinary desktop notification. No marmot on it, and it dismisses itself. |
auto |
A dialog only near a limit — the last mark, or a window burning faster than it refills. Banner otherwise. |
The banner caveats are not Marmot's choice. On macOS a notification's icon is
whichever app posted it, and how long it stays is that app's Alert style in
System Settings — neither is something display notification can set. The
dialog exists because it can do both.
On Linux none of this applies and nothing needs setting: a critical notification there already shows the marmot and already never expires.
marmot
Marmot · your last 30 days
40 sessions · everything below was read from ~/.claude/projects on this machine
Spend 4,184ドル modelled at published rates
Prompts you typed 926 per session: 23.1 mean · 16 median · 79 p99
Tokens 5.8B input, output and cache
Cache hit rate 98% higher is cheaper
Tool calls 13,768 3% failed
Baseline context 35.3K median, before you type
Daily ▃▂▄▁▃█▆▃▂▁▄▁▂▆▁▅▄▅ peak 671ドル · median 233ドル
Where it went
claude-opus-5 4,092ドル 98% · 5.7B tokens
claude-sonnet-5 92ドル 2% · 104M tokens
Tools failing
Bash 126 of 4,933 3%
mcp__supabase__execute_sql 3 of 3 100%
Skills
dataviz ×ばつ ~4.1K tokens to load
MCP servers
github ×ばつ 26 tools · ~4.0K tokens
sentry ×ばつ 14 tools · ~2.3K tokens ▲さんかく never called
18,825 tokens on every request, 16,262 of them idle
The report gives every figure a window and a source. Run marmot browse when
you want to open a session and follow a number down to the turn that caused it.
The browser is one local HTML file with no network calls; browse --no-text
leaves prompts and replies out.
marmot # report for the last 30 days marmot --days 7 # choose a window marmot browse # open the local session browser marmot nudges # show only actionable findings marmot sessions # list sessions one per line marmot mcp-audit # measure MCP tool-definition weight marmot config # open your thresholds marmot remind # show or change when nudges fire marmot doctor # check readers, hooks and notifications marmot test-notification # send a test nudge
Useful options:
marmot --sessions # include every session in the report marmot --json # machine-readable output marmot --no-audit # do not start MCP servers for measurement marmot --no-refresh # do not refresh plan-limit data marmot browse --no-text # exclude prompts and replies from the page
Run marmot --help for the complete reference.
| Signal | What it means |
|---|---|
| Long session | Old turns keep travelling into new ones |
| Stale session | Work resumes days later in a different area |
| Idle MCP server | Tool definitions are loaded but never used |
| Premium model on light work | A costly model handles a small task |
| Low cache hit rate | Context is rebuilt instead of reused |
| Tool failures | Calls fail and then need another turn to recover |
| Usage spike | Today is far beyond your own normal |
| Limit pace | Your allowance is disappearing faster than its window |
Every check is deterministic. No model decides whether to nudge you.
Most of those are causes rather than alarms. What interrupts you is a threshold—half, three quarters, then nine tenths of a plan window—and the nudge carries whichever cause best explains getting there:
▲さんかく 75% of your weekly limit
76% of your weekly limit is gone on Max ×ばつ. It resets in 2.1d.
Each turn re-sends 603K tokens of history, over 57 prompts and 3.8d.
Run /compact, or start a new session for the next distinct piece of work.
Causes are ranked by how much each explains, how confident Marmot is, and how
cheaply it can be fixed. marmot prints that ranking under Why it is going,
scores included. When nothing scores highly enough, the threshold still fires
without an invented reason.
Rules in the live list can interrupt at the end of a turn—the moment you can
still change the session in front of you. Everything else waits for the daily
digest.
A rule speaks once per session. Cost warnings can return when the cost doubles, and plan warnings return at the configured marks. One live nudge also buys 20 minutes of quiet before another can interrupt; held findings remain in the report and digest.
On a subscription, the dollar figure is labelled Modelled spend. It is what the tokens would cost at published API rates, not what you pay. Marmot also shows the plan limits Claude Code exposes locally:
Modelled spend 1,825ドル at API rates — not what you pay on Max ×ばつ
5-hour session limit 5% resets in 1.2h
Weekly limit 19% resets in 3.0d
Usage credits 0ドル.00 of 50ドル.00 real money, beyond the plan
On pay-as-you-go usage, the same dollar figure is labelled Spend because it is the bill. Marmot uses percentage limits when the plan reports them and dollar caps when it does not.
The defaults are deliberately quiet, so configuration is optional. If a rule fires on most of your sessions, it is describing how you work rather than flagging something unusual—raise the threshold instead of learning to ignore it.
marmot config set session.costCap=50 # change one threshold marmot config set 'limits.steps=[25,50,75]' # values are JSON marmot config set notify.bell=false mcp.autoAudit=false
Marmot prints what changed, creates the file from the defaults when needed, and leaves every other setting alone:
/Users/you/.claude/marmot.json
session.costCap: 25 → 50
This is the form to use from a script or coding agent. To edit or inspect the whole file:
marmot config # open it in your editor marmot config --print # print it in the terminal
Nothing needs restarting—the next run reads it. Marmot uses $VISUAL, then
$EDITOR, then the platform default. A terminal editor is used only when there
is a terminal to attach it to.
marmot remind # show what fires and when marmot remind --at 50,75,90 # set quota marks marmot remind --cap 100 # set a dollar ceiling marmot remind --off # turn reminders off
Marmot chooses the useful ceiling from the plan it can read:
| Plan | Ceiling |
|---|---|
| Pro, Max, most Team seats | Reported quota, at 50%, 75% and 90% by default |
| Enterprise or a plan reporting no quota | Daily dollar cap; session cap is half |
| Pay-as-you-go API | Dollar cap, because the figure is the bill |
Only one live nudge interrupts at a time. After one fires, Marmot leaves 20
minutes of quiet before another (interrupt.minGapMins). Held findings remain
in marmot and the daily digest.
Refreshing limits also captures Claude Code's own attribution of your usage:
What is driving your limits · last 7d
Claude Code's own attribution, over 2,594 requests in 19 sessions.
96% of your usage was at >150k context
80% of your usage came from sessions active for 8+ hours
top skills: claude-api 1%
top mcp servers: sprinto 1%
This is not inferred from transcripts. limit-drivers quotes Claude Code's
attribution when a share passes limits.driverMinPercent—60% by default.
The source is human-formatted text with no stability guarantee. Every line is optional; unrecognised lines are skipped, so a format change costs this section rather than the whole report.
limit-reached speaks at marks on the way to a limit, so you hear half gone
before nearly out. Each mark speaks once.
limit-pace compares the percentage used with the percentage of the window
that has passed. It warns only when the allowance is on course to run out before
it resets:
▲さんかく Spending your weekly allowance faster than it refills
43% through the weekly window with 78% of it gone — ×ばつ the pace that
would last. At this rate it runs out in about 20.3h, 3.2d before it resets.
"limits": { "enabled": true, "steps": [50, 75, 90], "byPlan": { "Pro": [50, 75, 90], "Max ×ばつ": [50, 75, 90], "Max ×ばつ": [50, 75, 90], "Team": [50, 75, 90], "Enterprise": [50, 75, 90], "API": [] } }
| Rule | Fires when | Default |
|---|---|---|
session-cost |
One session's modelled cost | > 25ドル |
daily-cost |
Today's total | > 50ドル |
daily-baseline |
Today against your trailing average | > 2.5σ over 14 days |
session-topics |
A long session resumed in a different area | > 1 day gap, ≥ 2 areas |
limit-reached |
A plan window crosses a configured mark | 50%, 75%, 90% |
limit-pace |
Allowance is disappearing faster than the window | > ×ばつ pace, ≥ 15% elapsed, ≥ 20% used |
limit-drivers |
Claude Code attributes a large share to one behavior | > 60% |
Idle MCP servers, subagent burn, carried history, quiet premium-model work and failing tools are now ranked as causes behind these thresholds. They explain a nudge instead of creating a second, duplicated alarm.
{
// Rules allowed to interrupt at the end of a turn.
"live": ["limit-reached", "session-cost", "daily-cost",
"daily-baseline"],
"interrupt": { "minGapMins": 20, "maxPerNudge": 1 },
// style: "alert" is a dialog that waits for you — the default for
// both. "banner" is the ordinary notification: no marmot on it, and
// it dismisses itself. "auto" is a dialog only near a limit.
"notify": { "desktop": true, "bell": true, "app": null,
"sound": "Ping", "persist": true,
"style": { "nudge": "alert", "digest": "alert" } },
"digest": { "cadence": "daily" },
"limits": { "enabled": true, "causeFloor": 0.08,
"steps": [50, 75, 90],
"autoRefresh": true, "paceRatio": 1.5,
"paceMinElapsed": 15, "paceMinUsed": 20,
"driverMinPercent": 60 },
"browse": { "keep": 5 },
"mcp": { "enabled": true, "autoAudit": true,
"auditMaxAgeDays": 7 },
"session": { "costCap": 25, "turnCap": 20, "costFloor": 1 },
"daily": { "costCap": 50, "baselineSigma": 2.5,
"baselineDays": 14 },
// USD per million tokens for negotiated pricing.
"rateOverrides": { "claude-opus-5": { "in": 5, "out": 25 } }
}marmot config writes every key with its default; these are the ones most
people need.
Optional statusline:
marmot init --statusline
12ドル.40 · 57 prompts · 41% ctx · 97% cache · Opus ▲さんかく
The statusline is separate because installing it replaces an existing Claude Code statusline.
Claude Code writes one JSONL entry per response content block, and each entry repeats the same usage object. Marmot counts usage once per API response; summing every entry would inflate a tool-heavy session by roughly ×ばつ.
Cache writes are also priced at their recorded lifetime: ×ばつ input price for five minutes and ×ばつ for one hour. Treating every write as the cheaper kind can understate a heavy session by about a fifth.
A turn means a prompt you typed. Tool results also appear as user entries in
Claude Code's records, but Marmot does not count them as human prompts. These
cases are pinned by the test suite.
- Local by design. Marmot has no account or hosted service.
- The report avoids conversation text. It reads counts, identifiers and tool names.
- The browser includes prompts and replies. Use
browse --no-textto leave them out. The generated page stays local either way. - MCP audit starts configured servers. Use
--no-auditor setmcp.autoAudit=falseif you do not want that. - Subscription dollars are estimates.
Modelled spendis the API-rate value, not your invoice; the plan-limit percentage is the useful ceiling. - Marmot is alpha. Claude Code's session format is internal and can change.
marmot doctorshows what remains readable.
The hooks run in a process Claude Code starts and reaps, so there is normally nothing to look at. Marmot keeps a log of what they did and why:
marmot logs # newest first, and whether the hooks are installed at all marmot logs --json # raw JSONL, oldest first — attach this to a bug report
Each run records the plan it read, the caps it compared against, and every rule's outcome:
2026年09月03日 09:45:58 Stop nothing to say
session 6d16e4fb · 33ドル.75 · 30 turns · 30 prompts
plan Max ×ばつ · weekly_all 6%
rule quiet session-cost — the rule did not match
That is usually enough to tell a wrong threshold from a rule that never ran.
It is capped, and local like everything else; marmot config set log.hooks=false turns it off.
marmot test-notification
If the test does not appear, check Focus or Do Not Disturb first. Then run
marmot doctor to see which notification path Marmot is using.
Both are dialogs by default, which sidesteps this. If you have set
notify.style to auto or banner, note that banners fade on their own — and
on macOS how long they last is the Alert style of whichever app posts them, not
something Marmot can set. Either set that app to Alerts in System Settings →
Notifications, or go back to dialogs:
marmot config set notify.style=alertKeep transcript nudges while disabling desktop notifications or sound:
marmot config set notify.desktop=false marmot config set notify.bell=false
npm install -g github:DrDroidLab/marmot # update marmot init --hooks --remove # remove Marmot hooks
npm testPlease work on a branch and open a pull request. main moves through reviewed
pull requests only.