Repository navigation
fix(coding-agent): keep git state out of the cached system prompt - #354
Merged
Merged
Conversation
ZouR-Ma
requested review from
MelodyVAR,
Uking-xxx and
liuedcson
as code owners
November 9, 2026 12:16
The Step system prompt's <env> block carried the git branch and the uncommitted-file count. Any prompt rebuild after a checkout (an MCP catalog refresh, a tool registration) re-read git and rewrote the system prompt, which the provider caches right after the tool block, so the whole conversation was re-billed as cache writes. Between rebuilds the model saw a stale branch. The branch now rides a runtime-context snapshot: a hidden custom message appended beside the user's prompt only when the branch differs from the last snapshot in the active context, so history stays append-only and the cached prefix survives a checkout. The context hook restores a snapshot that compaction summarized away, at the request tail only. The uncommitted-file count is dropped: it changes with almost every edit the agent makes, and the prompt already tells the model to inspect git state.
The runtime-context snapshot was read only on before_agent_start, so it covered user prompts alone. A run that checked out a branch through a tool, and runs started without a user prompt (goal continuation, cron, background subagent wake-ups, an approved plan, retries), kept showing the branch from the last user prompt. Mark the branch stale after a step that ran tools, and on agent_start when before_agent_start did not run. The context hook then re-reads git once and, when the request does not already show the current branch, appends the snapshot at the request tail without writing history; the next user prompt persists it. A user prompt still reads git once for its first request.
SDK hosts that compose buildStepSystemPromptAppendix without createStepCapabilitiesExtensionInline lost the git branch when it left the system prompt. Export createStepRuntimeContextExtension and its options so they can add it directly.
ZouR-Ma
force-pushed
the
fix/git-runtime-context
branch
from
November 9, 2026 13:28
4c0adb6 to
30727e3
Compare
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.
Problem
The Step system prompt's
<env>block carriedGit branchandUncommitted changes, read from git whenever the prompt was rebuilt. The prompt is not rebuilt per user message; it is rebuilt when tools or resources change (an MCP catalog refresh or restart, an extension registering tools,/reload). After a checkout, such an unrelated rebuild re-read git and rewrote the system prompt. Anthropic-style prompt caching matches the request prefix (tools, then system, then history), so that one rewrite turned the whole conversation into cache writes. Between rebuilds the model saw a stale branch, and a run that checked out a branch mid-run (the agent itself runninggit checkout) never saw the change.Reproduced with the real CLI in RPC mode against a recording Anthropic endpoint: switching branches changed nothing until an MCP server restart with an identical tool catalog, which then changed the system prompt hash while the tool hash stayed the same.
Changes
src/step/system-prompt.ts: drop the git lines, the synchronous git reads and their 5 s cache from<env>.encodeEnvironmentValueis exported for reuse.src/features/step-runtime-context.ts(new), registered instep-capabilities.ts:before_agent_start, read the branch with onegit symbolic-ref --quiet --short HEAD(exit 0 branch, 1 detached HEAD, 128 not a repository; anything else, such as a missing binary or timeout, announces nothing). Persist a hiddenstep-runtime-contextcustom message beside the prompt only when the branch differs from the newest snapshot in the active context. The snapshot says it supersedes earlier ones, so history stays append-only.before_agent_start(goal continuation, cron, background subagent wake-ups, approved plans, retries). Thecontexthook then re-reads git once and, when the request does not already show the current branch, appends the snapshot at the request tail without writing history. The same hook restores a snapshot that compaction summarized away. The next user prompt persists the new branch.src/index.ts: exportcreateStepRuntimeContextExtensionfor SDK embedders that usebuildStepSystemPromptAppendixwithout the capabilities extension.CHANGELOG.md: entry under Unreleased → Changed.Behavior and compatibility
Git branchorUncommitted changes, so it and the tool block stay byte-identical across checkouts and unrelated rebuilds.<system-reminder>snapshot instead. A detached HEAD is stated as such, and leaving a repository after a branch was announced produces a "not inside a git repository" snapshot. Outside a repository, nothing is announced.buildStepSystemPromptAppendixlose the branch line unless they addcreateStepRuntimeContextExtension.Validation
Commits, each passing the pre-commit
pnpm run check:fix(coding-agent): keep git state out of the cached system prompt: prompt change, per-prompt snapshot, compaction restore, tests.fix(coding-agent): refresh the git branch within a run: mid-run and promptless-run refresh, tests.feat(coding-agent): export the runtime-context extensiondocs(changelog): record the git runtime-context changebuild-check-testandCheck commit attribution: passed.packages/coding-agentat commit 1 (34/34) and at the tip (40/40):pnpm exec vitest --run test/step-runtime-context.test.ts test/suite/regressions/git-runtime-context-cache-prefix.test.ts test/step-system-prompt.test.ts test/step-capabilities-extension.test.ts test/suite/regressions/plan-mode-prompt-cache-prefix.test.ts.test/json-mode-durability.test.tswith exit 143 (its 60 sspawnSynccap killed the CLI child).mainrun 42374504621 failed the same way in that file without this change. Locally the file passes, about 6 s per case even with every core saturated, and a network trace of the same child shows only local IPC connections, so I did not change that test here.test/suite/regressions/git-runtime-context-cache-prefix.test.tsdrives a real session with real git:system-prompt.ts(not to contain 'Git branch:').git checkoutinside one run showsGit branch: devat the tail of the next request with an unchanged prefix. It fails against commit 1's extension../test.sh(macOS):$TMPDIR,test/subagent-invocation.test.tsfailed withlisten EINVAL: tsx's IPC socket path under the isolated temp root is 113 characters, over the macOS socket path limit. The file passes (4/4) when run directly.TMPDIR=/tmp ./test.sh: scripts, config, contracts, telemetry, lark-adapter, providers (484 passed), agent-core (560 passed), coding-agent (5342 passed) and tui passed.apps/clihad 1 failure intest/edit-tool-no-full-redraw.test.ts("keeps the applied change on a Step card whose call failed after writing"), a render-timing assertion in the edit tool card. The file passed 5/5 in three consecutive direct runs; it does not touch the system prompt or this extension.Limitations
git symbolic-refcall (bounded by a 1.5 s timeout).Today's dateremains in<env>; crossing midnight still rewrites the system prompt on the next rebuild, at most once a day.