System prompt size grew ~70K tokens between v2.1.89 and v2.1.96, making sessions unusable without frequent manual /compact
Summary
Between Claude Code 2.1.89 and 2.1.96 (approximately 5 days, early April 2026), the initial system prompt size grew by approximately 70K tokens. This is large enough to make sessions effectively unusable: with auto-compact disabled (as I had it configured at the time), the context would fill before I could complete any non-trivial task, and Claude Code stopped accepting input entirely. I had to introduce a manual /compact workflow as an emergency measure just to keep working.
Environment
- Claude Code versions affected: 2.1.89 → 2.1.91 → 2.1.92 → 2.1.96
- OS: Windows 11 (Git Bash shell)
- Models: claude-opus-4-6 / claude-sonnet-4-6
- Setup: standard user install + a few user plugins (LSPs, code-review, feature-dev, hookify)
Measurement Method
I correlated the usage.cache_creation_input_tokens field of the first assistant message in each session JSONL against the version field, across all sessions under ~/.claude/projects/<id>/*.jsonl.
The first assistant message has no cache hit yet, so its cache_creation_input_tokens reflects the full initial system prompt assembled by the binary — independent of any additionalContext injected by UserPromptSubmit hooks.
import json
from pathlib import Path
from collections import defaultdict
proj = Path.home() / ".claude" / "projects" / "<your-project-id>"
by_version = defaultdict(list)
for jsonl in proj.glob("*.jsonl"):
first_cache = None
version = None
with jsonl.open(encoding="utf-8") as f:
for line in f:
try:
d = json.loads(line)
except json.JSONDecodeError:
continue
if version is None and d.get("version"):
version = d["version"]
usage = (d.get("message") or {}).get("usage") or {}
cct = usage.get("cache_creation_input_tokens")
if cct and first_cache is None:
first_cache = cct
break
if first_cache and version:
by_version[version].append(first_cache)
for v in sorted(by_version):
vals = sorted(by_version[v])
print(f"{v}: n={len(vals):3d} median={vals[len(vals)//2]:6d} max={max(vals):6d}")
Observed Data
| Date | CC Version | Median first-cache | Δ vs baseline |
|--------------|-----------------|--------------------|---------------|
| 04-01..04-02 | 2.1.89 / 2.1.90 | ~38–52K | baseline |
| 04-03 | 2.1.91 | ~80–88K | +40K |
| 04-04..04-07 | 2.1.92 | ~85–90K | +40K (stable) |
| 04-08 | 2.1.96 | ~112–119K | +70K total|
Two distinct step changes: +40K at 2.1.91, +30K at 2.1.96. Total growth: ~70K in 5 days.
Impact
I had auto-compact disabled during this period (I was testing with it off). As the system prompt grew past ~100K+, sessions would fill up before completing any multi-step task. Claude Code stopped accepting new input. I had to run manual /compact continuously as an emergency workaround just to keep the tool usable. This persisted until I re-enabled auto-compact and tuned the threshold.
Even with auto-compact enabled at the default threshold, the effective usable context has shrunk dramatically: a 70K larger baseline means 70K less room for actual conversation and tool output.
What I Already Ruled Out
- UserPromptSubmit hooks (
additionalContext): affects later cache blocks, not the firstcache_creation_input_tokens. Verified by disabling my hook chain — first-cache size unchanged. - MCP servers: removed candidate MCPs, re-measured — first-cache size unchanged.
- Plugin count: removed 5 stale LSP plugins, re-measured — first-cache size unchanged.
- Session state / bistability: sessions started with
/clearshow ~50–60K smaller first-cache than sessions resuming work. Both classes show the same version-correlated step changes, so the jumps are not session-state artifacts.
The growth correlates exactly and exclusively with binary version bumps.
Suspected Sources of Growth
Based on inspection of what appears to have changed in the system prompt area between these versions:
- Skill listing attachment (
skill_listingtype) — my session shows ~7KB for 47 skills injected on every turn - New or expanded injection-defense / safety layers
- New tool descriptions (Skill tool, browser-automation tool suite, Plugin subagent descriptions)
Asks
- Confirm whether the ~70K growth between 2.1.89 and 2.1.96 is intentional and which components account for it.
- Consider lazy-loading or deduplicating large fixed blocks (skill listings, agent descriptions, plugin metadata) so they only appear when the relevant capability is actually needed.
- Expose first-cache / baseline system-prompt size in
/statusor a debug flag so users can monitor it without parsing session JSONLs. - Document the expected baseline range per version in the CHANGELOG so users can anticipate and plan around context budget changes.
Reproducibility
Anyone with multiple session JSONLs spanning versions 2.1.89–2.1.96 can run the script above and reproduce the version-vs-size correlation in a few seconds.
9 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
such an impact, and the old issues of other users aren't fixed till now. look at your full issue list.
maybe not other harness users impact your system, maybe claude code itself.
Solid methodology — using first-message
cache_creation_input_tokensto measure the full system prompt before any caching kicks in is clean.We attempted to reproduce the version-correlated growth on our minimal setup (v2.1.92 → v2.1.96, ~8 skills, 3 plugins, 1 MCP) and found no change — the system prompt and tool schemas are byte-identical between the two versions for our configuration. This suggests the growth you're seeing is amplified by plugin/skill count. With 47 skills, each version that adds new tool descriptions, skill metadata, or injected blocks (keep-coding-instructions, safety layers) compounds across all of them. The base system prompt and tool schemas haven't grown in isolation — it's the interaction with a larger plugin surface area.
That said, the per-turn cost of that surface area is real regardless of version. Two observations from our cache-fix interceptor work:
Your ask #3 (expose baseline prompt size in
/status) would also be valuable. Right now the only way to measure it is your JSONL parsing approach or intercepting the raw API request.Follow-up — we dug deeper and found our initial version comparison was flawed (we were comparing interactive vs one-shot system prompts). After installing v2.1.92 and v2.1.96 side by side and capturing the raw API payloads, the system prompt and tool schemas are byte-identical between the two versions on our minimal setup (~8 skills, 3 plugins).
This means the growth you're seeing is driven by your plugin/skill surface area, not the base system prompt. Each version that adds new capabilities (keep-coding-instructions in 2.1.94, plugin skill hooks, etc.) injects additional metadata per-plugin. With 47 skills, each increment compounds.
We've added prompt size measurement to the interceptor (8c6a1de) — with
CACHE_FIX_DEBUG=1, every API call now logs:On our minimal setup, the skills listing is 1,627 chars. With 47 skills, that number should be significantly larger. If someone with a heavy plugin setup can share their
PROMPT SIZElog line, we'll have the data to justify adding optional skills listing stripping as a feature — similar to our existing image stripping (CACHE_FIX_IMAGE_KEEP_LAST).Seeing the same behavior — on a moderately active session the full skill listing gets injected after every tool result (8+ times in one session, ~3–4k tokens each). SessionStart already loaded the full list once, so the per-turn re-injection feels redundant.
Would love a
settings.jsonopt-out (e.g.skipPostToolSkillReminder: true) to keep it SessionStart-only. ReducingenabledPluginshelps linearly but isn't a substitute — skills I want available for occasional use still pay the per-turn cost.@AlfredGuquan The skill listing duplication on resume is a known cache-busting vector — the blocks scatter to different message positions across turns, changing the prefix bytes. We've documented the mechanism and it's one of the fixes in the community interceptor (
relocateextension pins them to a stable position inmessages[0]).If you're on the npm install of CC (v2.1.112 or earlier), the README has setup instructions. The debug log (
CACHE_FIX_DEBUG=1) will showrelocate=activewhen it fires.The root cause is CC's
normalizeResumeMessagespath — on resume, attachments that were inmessages[0]on the first call get scattered to later messages, and the skill listing is regenerated at a different position each time. Our issue #12 tracks the broader investigation into prefix-cacheable block placement.Closing for now — inactive for too long. Please open a new issue if this is still relevant.
Additional operator datapoint from the local-runtime side:
This overhead hurts disproportionately when Claude Code is being used as a bridge into a local long-context stack, because the fixed prefix is competing directly with the context budget you are paying for on your own hardware.
In my case, once the baseline prompt gets materially larger, the practical effect is not just "slightly fewer turns". It changes whether 64k-class local sessions stay usable before compaction / summarization pressure kicks in. That is one of the reasons I ended up keeping a direct local runtime path for serious runs and using Claude Code more selectively.
So even if the absolute growth looks modest from a cloud-first perspective, on local / bridged workflows the fixed system-prompt tax is very visible. Reducing always-on static blocks, lazy-loading capability metadata, and avoiding repeated reminder injection would have a real usability impact.
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.