Users are billed for ~24M hidden input tokens/month per heavy seat — silent session resumes re-fire SessionStart hooks + re-send MCP instructions, with no UI disclosure or consent

Resolved 💬 3 comments Opened Apr 19, 2026 by scokeepa Closed May 27, 2026

Summary

On Claude Code v2.1.114, sessions silently "resume" without user action, re-firing every SessionStart hook and re-attaching every MCP server's instructions field. This repeatedly invalidates the prompt cache and re-creates thousands of tokens' worth of system-prompt prefix every few turns, with no UI indication and no user consent. Heavy users are billed for millions of hidden input tokens per month.

This is a billing transparency violation: users pay for tokens they did not knowingly cause and cannot observe in the UI.

Reproducibility — specific combination required

This does not manifest on every user's install. It shows up strongly when all three of the following are present simultaneously:

  1. One or more MCP servers that set an instructions field (e.g. claude-peers, and many community MCP servers). Each connection re-emits the instructions into the system prompt.
  2. Multiple SessionStart hooks configured in ~/.claude/settings.json whose additionalContext outputs are non-trivial (skill catalog, model profile, watcher session banner, etc.).
  3. Claude Code's internal session-resume behavior in v2.1.x, which silently re-enters the same session in the background and re-fires SessionStart without the user typing claude --resume or restarting.

Users with a vanilla install (no MCP instructions, no hook additionalContext) will not see large cache churn, which is likely why this has gone unreported. The issue is the interaction between a legitimate extensibility surface (hooks/MCP) and an undocumented resume behavior.

Observed evidence (this machine, one ~5.7h session)

Transcript: ~/.claude/projects/<cwd>/fd839c9f-34e7-42ce-aa65-121fbbdc1814.jsonl

  • 49 SessionStart re-fires in a single session (expected: 1)
  • 26 cache resets: cache_read_input_tokens drops to a 16,520 floor while cache_creation_input_tokens spikes ~22,000 each time
  • 0 PreCompact / PostCompact events — this is NOT auto-compaction, this is silent re-caching
  • Per-turn banners visibly repeat in the terminal (screenshots attached): Listening for channel messages from: server:claude-peers / Experimental — inbound messages will be pushed into this session re-prints between turns even though the user never restarted

Heavy-user billing impact

Conservative per-session assembly (measured on my install):

  • SessionStart hook outputs: ~4–8 KB
  • MCP instructions (claude-peers alone): ~2.1 KB
  • Hook-driven additionalContext from UserPromptSubmit: ~1–3 KB / turn
  • Per-resume cache-creation: ~22,000 tokens observed

Heavy user estimate (8h/day, ~50 resume-cycles/day, 22 working days/month):

  • 22,000 tokens × 50 × 22 = ~24.2M cache-creation tokens / month / seat
  • At Opus 4.7 input cache-creation rate: 24.2M × \$6.25/MTok ≈ \$151/month/seat of hidden billable input that the user neither authored nor saw.

Even halving every assumption (~12M tokens/month) this is ~\$75 of invisible input per seat per month — enough to matter for any team running Claude Code daily.

Why this matters beyond dollars

  1. Billing transparency violation — users are billed for context they never typed, never saw in the UI, and cannot disable without removing features (MCP, hooks) that the product actively encourages.
  2. Prompt-cache model is broken by design here — caching is sold as a cost saver, but silent re-injection repeatedly invalidates exactly the prefix that should stay cached.
  3. Auto-compact fires earlier than the user expects — because the input prefix balloons silently, users hit the compact threshold mid-task for reasons unrelated to their own work.
  4. No disclosure — there is no CLI flag, settings entry, or docs page that says "resuming a session re-runs all your SessionStart hooks and re-sends MCP instructions."

Ask

  1. Disclose the session-resume behavior in docs, and surface in the UI when a session is silently resumed vs. freshly started.
  2. De-duplicate MCP instructions and SessionStart hook additionalContext across resumes within the same logical session (they should be cached as part of the stable prefix, not re-created each resume).
  3. Add a counter / /cost breakdown that shows how many tokens came from hook additionalContext vs. user text vs. tool output, so heavy users can audit what they are being billed for.
  4. Fire SessionStart once per session ID, not once per silent resume.

Mitigation I had to apply locally

I patched ~/.claude/hooks/edit-counter.js so its PostToolUse reminder writes to a log file instead of calling contextOutput(). That alone eliminated ~17.5 KB / session of injected text. But this is a workaround for a single hook — the structural issue (resume re-firing everything) is still there.

Environment

  • Claude Code: v2.1.114
  • Model: Opus 4.7 (xhigh effort)
  • Plan: Claude Max
  • OS: macOS 25.4.0 (Darwin arm64)
  • MCP servers active: claude-peers (provides instructions)
  • Hooks: 5 SessionStart, 4 UserPromptSubmit, 10+ PreToolUse:Bash (standard productivity hook set)

Attachments

Two redacted terminal screenshots are attached showing the repeated Listening for channel messages / Experimental — inbound messages will be pushed banners re-printing between turns within a single uninterrupted session. Personal paths have been masked as ~/PROJECT / <redacted-path>.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗