[BUG] `--resume` drops the session's `--effort` level, invalidating the prompt cache
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Start a session with --effort low, then resume it with --resume. The resumed turn does not run at low — it runs at whatever effort the resuming invocation resolves to.
On resume, effort is taken from the current invocation, not from the session. If --effort is omitted on the resume, the turn falls back to the built-in default effort; if --effort is set to a different level, that level is used. Either way the session's low is lost. (This is proven by the control below: re-passing --effort low on the resume restores full cache reuse, while omitting it does not.)
This happens easily in normal use: start a session at --effort low, then claude --resume <id> without re-passing --effort — it comes back at the default effort, not low.
Because the resumed request now differs from the request that created the session — same model, same --append-system-prompt, but a different effort — the prompt cache is invalidated. In the repro below, cache reuse on the resumed turn drops to ~29% (the session's cached prefix is largely rewritten), even though the only input that changed is the effort level.
What Should Happen?
--resume should restore the effort the session was created with, so the resumed turn reuses the cached prefix instead of rewriting it.
Claude Code already does this for the model: the 2.1.144 changelog notes that "Resumed sessions now keep the model they were using instead of picking up another session's /model choice." --effort is the same class of per-session generation setting — claude --help describes it as the "Effort level for the current session" — so resume should preserve it the same way, rather than silently re-resolving it from the current invocation.
Error Messages/Logs
Steps to Reproduce
- Start a session with a large
--append-system-prompt(so a cache block is created) at--effort low:
claude --session-id <id> --model "claude-sonnet-4-6" --effort low --append-system-prompt "<~8k tokens>" -p "hi"
- Resume it without re-passing
--effort:
claude --resume <id> --model "claude-sonnet-4-6" --append-system-prompt "<~8k tokens>" -p "hi"
- Compare the cache reuse across the two turns.
- Observe the first turn writes the cache and the resumed turn reuses only ~29% of it, re-creating the rest.
- Repeat with
--effort lowalso on the resume and the resumed turn reuses ~100% of the cache.
The model is identical on both turns (claude-sonnet-4-6) and the --append-system-prompt is byte-for-byte the same, so this is not the [1m]/model-drop issue — the only input that differs between the two turns is the effort level.
What metadata to check — run with --output-format stream-json --verbose and read:
- the final
resultevent'susage.cache_read_input_tokens/usage.cache_creation_input_tokens
The same fields are in the session transcript at ~/.claude/projects/*/<session>.jsonl. Effort itself is not recorded anywhere in the transcript or the init event — the per-request usage block logs service_tier and speed but no effort field — so the cache-reuse delta is the only observable tell that the resumed turn ran at a different effort.
Simple script to reproduce — two scenarios differing only in whether the resume re-passes --effort:
#!/usr/bin/env python3
"""`claude --resume` drops the `--effort` level, breaking prompt-cache reuse on resume.
The two scenarios below differ only in whether the resume re-passes --effort:
resume without --effort -> resume loses effort -> ~29% cache reuse (bug)
resume with --effort low -> resume keeps effort -> 100% cache reuse (works)
claude-sonnet-4-6 so the model is identical on both turns and the only variable is
effort (sonnet supports effort low/medium/high; max is Opus-tier only)."""
import json, subprocess, tempfile
from uuid import uuid4
model = "claude-sonnet-4-6"
base = ["claude", "-p", "--output-format", "stream-json", "--verbose",
"--model", model, "--setting-sources", "project", "--strict-mcp-config",
"--tools", "", "--append-system-prompt", "cache-marker " * 1500]
def run(extra, cwd):
out = subprocess.run(base + extra + ["hi"], cwd=cwd, check=True,
capture_output=True, text=True).stdout
events = [json.loads(l) for l in out.splitlines()]
return next(e["usage"] for e in events if e.get("type") == "result")
def scenario(label, resume_extra):
session = str(uuid4())
with tempfile.TemporaryDirectory() as cwd:
first = run(["--session-id", session, "--effort", "low"], cwd)
resumed = run(["--resume", session] + resume_extra, cwd)
avail = first["cache_read_input_tokens"] + first["cache_creation_input_tokens"]
read = resumed["cache_read_input_tokens"]
print(f"{label}: resume reused {read}/{avail} ({read / avail:.1%})")
scenario("resume WITHOUT effort (bug) ", []) # bug
scenario("resume WITH --effort low ", ["--effort", "low"]) # control
output:
resume WITHOUT effort (bug) : resume reused 2136/7396 (28.9%)
resume WITH --effort low : resume reused 7397/7397 (100.0%)
Claude Model
Sonnet (default)
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.168
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
VS Code integrated terminal
Additional Information
This is the effort-level analog of the [1m]-modifier resume bug (#65805), with a difference in mechanism. For [1m], resume restores the base model from the transcript but drops the recorded [1m] modifier (a partial restore of recorded state). For effort, there is nothing recorded to restore from — effort isn't written to the session, so resume always takes it from the current invocation. In both cases the resumed request ends up differing from the request that created the session, so the cached prefix is invalidated.
The partial reuse on resume (~29% above) is the portion of the prefix ahead of where effort perturbs the request; everything after it is re-created. (Contrast the [1m] bug, where the model string changes at the very front of the prefix and reuse drops to 0%.) Re-passing the same --effort on resume restores 100% reuse, which is what isolates effort as the sole cause.
More generally: because the session's effort is not preserved across resume, the resumed turn silently runs at a different effort whenever --effort is omitted (or set differently) — and any such effort change invalidates the cached prefix. The fix is to persist the session's effort and reapply it on --resume/--continue, exactly as the model now is per the 2.1.144 changelog.
Showing cached comments. Read the full discussion on GitHub ↗
7 Comments
Related: [[BUG]
--resumedrops the Opus[1m]modifier, invalidating the prompt cache](https://github.com/anthropics/claude-code/issues/65805)Found 1 possible duplicate issue:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Not a duplicate. The 'checklist' says to "file separate reports for different bugs", so I did.
is this message related?
Depends on when that text is showing up. When the effort level is changed, it can't use the existing cache (like that message is saying). The reported bug is specifically about it not restoring the proper effort upon resuming.
Confirming this is still happening, and want to add a more consequential real-world example than cache efficiency alone.
I have a long-running Claude Code session that's been resumed many times over several weeks (terminal closed/reopened repeatedly,
claude --resumeeach time). I set the session tomediumeffort early on and never touched it again — but weeks later, discovered via direct transcript inspection that the session was actually running athigheffort the whole time. Model correctly persisted asclaude-sonnet-5across every resume (matches the fix already shipped for model in 2.1.144), but effort silently reverted to whatever a fresh terminal launch resolves to by default, exactly as this issue describes — I never explicitly changed it, and the only/effortinteraction in the entire session transcript was a cancelled one (opened the picker, saw the current — apparently already-wrong — selection, closed it without confirming).The consequence went beyond wasted cache:
higheffort generates substantially more tokens per turn (extended thinking). On a long-running session, that inflates the token cost of every turn, which shrinks how much actual conversation the compaction mechanism's preserved-tail window covers (the tail is token-bounded, not message-count-bounded). In my case this contributed to a real information-loss incident — a decision agreed on just a couple of messages before a compaction event fell outside the preserved tail and got lost, because those "couple of messages" were far bulkier in tokens than they'd have been at the effort level I actually intended.So this isn't just a cache-reuse efficiency bug — for long-running, frequently-resumed sessions, it can silently and cumulatively escalate effort level far beyond what the user set, with downstream consequences for context retention across compaction, not just cost. Given model already got the "resume preserves session state" fix, effort seems like a natural extension of the same fix rather than a separate effort.
It looks like the related variant [[BUG]
--resumedrops the Opus[1m]modifier, invalidating the prompt cache](https://github.com/anthropics/claude-code/issues/65805) was auto-closed.