[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.
This issue has 7 comments on GitHub. Read the full discussion on GitHub ↗