Repository navigation
fix: Claude Code loses the caller's system prompt after a resume - #203
Draft
timothee-chauvin wants to merge 1 commit into
Draft
timothee-chauvin wants to merge 1 commit into
timothee-chauvin wants to merge 1 commit into
Conversation
Claude Code does not keep --append-system-prompt in its session, and resumed launches omitted it, so every --resume (another attempt, an uncaught-error retry, a checkpoint resume) ran on Claude Code's default prompt alone. pin_system_prompt_filter restores it only while the first user message is unchanged, which Claude Code's compaction breaks. Record the caller's system messages before the session's first launch and append them on every launch.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 Claude Opus 5.5 says:
Claude Code doesn't keep
--append-system-promptin its session.claude_code()passed the caller's system messages (andsystem_prompt=) with that flag only on a session's first launch. So every--resume(another attempt after an incorrect answer, an uncaught-error retry, a checkpoint resume, a second turn) ran on Claude Code's default prompt alone.pin_system_prompt_filter(#142) restores the first launch's system prompt on resumed requests, but only while the request's first user message is unchanged. Claude Code's compaction replaces that message with its summary, so for a session that compacted before it resumed, the caller's prompt is gone for the rest of the sample, and no warning is logged. We hit this in a 48-hour benchmark run on 0.2.70: after an incorrect submission at 16.5h, every later model call ran without the task's instructions and rules, and the agent then did something those rules forbid.This PR records the caller's system messages in the sample store, keyed by session id, before the session's first launch, and passes them with
--append-system-prompton every launch. It doesn't re-read them fromstate.messageson later launches: by then the bridge has replaced them with Claude Code's own prompt, and appending that duplicates it (the bugtest_claude_code_system_prompt_not_duplicatedguards against, whose fix removed appending on resume).Against the real CLI (Claude Code 2.1.236, local fake Messages API), a
--resumewith--append-system-promptsends the appended text exactly once and keeps the first launch's identity line ("You are Claude Code, …"). Without the flag, the text is missing and the identity line flips to "You are a Claude agent, built on Anthropic's Claude Agent SDK." So the identity-line difference the pin filter's docstring listed came from the missing flag; the docstring now says so.Behavior changes:
--append-system-prompt. The pin filter still applies while the first user message matches, and the first launch and resumes now send the same appended text.state.messagesand appends Claude Code's own prompt once more on that resume.Validation
test_every_launch_appends_the_callers_system_prompt: it runs the real launch loop with a fake sandbox and bridge, two turns of one agent withattempts=2, and asserts every launch appends exactly the caller's prompt. It fails onmain([] == ['Task prompt']). It also fails if the prompt is rebuilt fromstate.messageson each turn (Claude Code's prompt plus the task, appended again).pytest(fast suite, Python 3.12): 414 passed, 86 skipped, 2 failed. Both failures also fail onmainhere:test_bundled_catalog_tracks_live_latest(the bundled Codex catalog is stale) andtest_k8s(no Anthropic credentials).test_home_isolation.pyandtest_trajectory_parsing.pywere not collected because I didn't install the optional mini-swe-agent dependencies (thedevextra'squarto-clibuild fails on this host).mypy src tests: the same 8 errors asmainin this environment, all missing optional packages.test_multi_call.pytests (need an Anthropic key and Docker).Agent review
codex exec -s read-only(fresh context, different model family; model checked in its session log), and Claude Opus 5.5 subagent (fresh context), 1 pass each.session_idis shared by every sample using one agent instance.