Dev.to AI 🤖 Ai 👁 0 📖 5 min read

My Claude Max meter said 2%. I'd barely used it, so I went digging.

My Claude Max weekly limit reset at 6 in the morning. Around 1 PM I looked at the usage meter and it said 2%. I hadn't done much that morning. Definitely not 2% worth. It's a small number, I know, but it bugged me. If s

My Claude Max weekly limit reset at 6 in the morning. Around 1 PM I looked at the usage meter and it said 2%.

I hadn't done much that morning. Definitely not 2% worth. It's a small number, I know, but it bugged me. If something is spending my limit, I want to know what.

Quick note before I start: I did most of this digging with Claude Code itself, and wrote the fix with it too. So yes, this is a story about using an AI tool to figure out what another AI tool was costing me.

First, what did I actually use?

Claude Code saves a transcript for every session, and each one records token usage. So I added up every transcript on my machine since the reset.

It came to about 0.26% of my weekly limit.

My own work explained maybe an eighth of what the meter showed. Whatever spent the rest wasn't writing transcripts.

I also wanted to make sure the meter wasn't just wrong. Claude Desktop keeps a history of it in ~/Library/Application Support/Claude/plan-usage-history.json: 0% at 06:00, 2% at 12:59. So it really did happen that morning.

The only thing running in the background

I had one plugin that does work in the background: claude-mem. It's a popular memory plugin. It watches your tool calls and passes them to a background "observer" Claude, which writes memory notes for your future sessions.

That observer runs as claude --no-session-persistence, which means it doesn't write a transcript. That matched exactly what I was missing.

The plugin's worker has a status endpoint, so I checked it. /api/processing-status returned queueDepth: 2080. My last session had ended about seven hours earlier, and it was still working through a queue.

What the logs showed

The plugin's own logs had the whole timeline (GMT+6):

Time What happened
02:34 Its quota guard pauses at 93% weekly usage. Work gets buffered, not dropped.
05:37 3,285 tool calls are waiting in the buffer.
05:46 The weekly limit runs out.
06:00 The limit resets to 0%.
06:08 The plugin resumes and starts on the backlog, now billed to the new week.
06:00–07:00 677 observer turns in that one hour.

So pausing near the limit didn't save anything. The work just got charged to the next week.

Not dropping the work makes sense for a memory tool, since you don't want gaps in your notes. But on a capped plan, it means the backlog gets paid for at the start of your fresh week.

Why each turn cost so much

Volume was one problem. The other was that every single turn was heavy.

The observer re-reads its whole conversation on every turn. That conversation grows to around 100K tokens before it resets, and by default it handles one tool call per turn. When I lowered CLAUDE_MEM_OBSERVER_MAX_CONVERSATION_CHARS from 400000 to 100000, turns dropped to about 26K tokens. That's a setting, not a bug, so I left it in the issue for the maintainer.

The second part was a real bug. When Claude Code edits a file, the Edit result includes originalFile, which is the entire file as it was before the edit. In the plugin's logs I found 7 calls in 44 seconds, each around 67.5K input tokens, because the file being edited was about 210 KB. Each call produced about 150 tokens of output, with zero cache reuse.

claude-mem already had code to cut originalFile out of Edit results. So why was it still being sent?

The bug

The trim had a safety check. It only ran if every key in the Edit result was one it recognized:

Object.keys(output).some(k => ![
  'filePath', 'oldString', 'newString', 'originalFile',
  'structuredPatch', 'userModified', 'replaceAll',
].includes(k))
// any unknown key → skip the trim, send the raw payload

Claude Code 2.1.309 added two new fields to Edit results: contentNotInModelContext and staleRecovered. Both are just true/false. But they weren't on the list, so the check treated the whole result as unfamiliar and sent it as-is, whole file included.

No error, no warning. The trim just stopped running after a Claude Code update, and nobody noticed.

I think the check made sense when it was written. If you don't know what a field contains, it's safer not to touch the payload. It just didn't expect upstream to add harmless fields.

The fix

Before changing anything, I replayed the trim logic on real Edit results stored on my machine to make sure this was really the cause. It was.

Then I made two small changes in that function (PR #5027):

  1. Unknown keys whose values are booleans, numbers or null no longer stop the trim. A true/false can't contain source code, so there's nothing to protect. An unknown string, array or object still takes the careful path. This also covers whatever flag Claude Code adds next.
Object.entries(output).some(([k, v]) => !KNOWN_KEYS.includes(k)
  && v !== null && typeof v !== 'boolean' && typeof v !== 'number')

(The real code has the key list inline. I shortened it here.)

  1. If the trimmed version is still over the size budget, it now gets condensed. Before, it fell back to the raw payload, whole file and all.

Tests went from 4 passing and 2 failing to 6 out of 6, with typecheck and lint clean. When I replayed the new logic on 639 real stored Edit results, 168 got trimmed, up from 29.

The maintainer merged it about 15 hours after I opened the issue, and left this comment:

"Point 2 (Edit payloads carrying the full originalFile into condense) is fixed by #5027, written by @smrifat1411, who also reported this … It's on main and ships in the next release. Thanks for such a thorough report."

That was nice to read. The other points in the issue (default observer size, usage visibility, the backlog replay) are design decisions, and those are up to the maintainer.

What I did on my own machine

I cleared the backlog. Then I uninstalled claude-mem and switched to Claude Code's built-in memory (CLAUDE.md plus auto memory), which doesn't make any background calls. That's about my capped plan, not about the plugin. It's a good project, and the maintainer was quick and kind about all of this.

I wasn't the only one who ran into this. Other people had reported big background usage in the repo's issues: ~76.7M tokens in a day (#2315), ~62% of a Max 20x weekly cap (#2451), ~649M tokens (#2643). I haven't verified those numbers myself, and most of those people just uninstalled. As far as I could find, mine was the first report of the backlog getting replayed after the weekly reset.

What I'm taking away from this

If a background tool runs on your subscription, it uses your limit even when you're not working. I knew that in theory. I just hadn't really thought about it.

Pausing isn't the same as skipping. Buffered work still runs eventually, and you still pay for it.

When the meter and your own logs don't agree, look for whatever isn't writing logs. That gap is how I found all of this.

And allow-lists can break quietly. If you reject anything unfamiliar, think about what an unknown field could actually do. A boolean was never going to hurt anyone.

The issue has the full report if you want the details:

📰 Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.