feat(daemon): sync the workspace to its configured branch instead of pulling - #1829
feat(daemon): sync the workspace to its configured branch instead of pulling#1829zfy0701 wants to merge 1 commit into
Conversation
…pulling The session-start pull and the console's pull button both ran `git pull --ff-only` INTO whatever branch the checkout happened to be on. A shared checkout an agent had parked on its own branch therefore never came back: every fast-forward failed, the failure was swallowed as offline degradation, and the agent worked on a month-old base while the console showed that branch's HEAD as "the latest commit". `syncWorkspaceRef` replaces `pullWorkspaceRef`: fetch the configured branch, then `checkout --no-track -B <branch> refs/remotes/origin/<branch>`. The local branch ends identical to the remote one — never a merge, so history cannot fork — and uncommitted edits ride along the way `git checkout` carries them. Two cases refuse as data instead of losing work: a local commit the remote lacks (the branch is left alone and the detail says to push or move it), and an uncommitted edit touching a file the sync would rewrite (git's own refusal, naming the file). The result names the branch it switched away from. The console button is now "Sync", its result copy reads "Synced main — updated N files." / "Switched from X to main — …" / "Already in sync.", and a request-level failure is explained like every other console git write: the daemon's "agent is working in this workspace" refusal reaches the browser as 409 `WORKSPACE_STALE` rather than a 503 that read as an offline daemon. Wire contract and route path are unchanged; the cluster volume's branch proof keeps its shape, since a refused sync still leaves the volume where it was. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
One blocking regression: cluster workspace syncs now call git checkout, but the sandbox executor explicitly excludes checkout from its permitted inventory. Session-start syncs therefore silently leave the checkout stale, and console syncs return a refusal. Please wire the new operation through the shim executor before merging.
Verified the exact command against createExecHandler: ExecRefusedError: git checkout is not in the permitted inventory. All 187 focused daemon and control-plane unit tests passed; the database integration test could not run because Docker is unavailable.
sent by review-bot (Codex · gpt-6-astra) · open in session
| ) | ||
| } | ||
| } | ||
| await git.raw(['checkout', '--no-recurse-submodules', '--no-track', '-B', branch, tracking]) |
There was a problem hiding this comment.
[P1] Permit the new checkout operation in the sandbox executor
ShimGitRunner.raw() sends this command through createExecHandler, whose ALLOWED_GIT_SUBCOMMANDS in packages/daemon/src/shim/exec-handler.ts explicitly excludes checkout. Calling that handler with these exact arguments returns ExecRefusedError: git checkout is not in the permitted inventory. Consequently, every cluster sync reaching this step fails, even for a clean checkout already on the configured branch: session-start preparation swallows the error and stays stale, while the console reports a refusal. Please update the shim execution contract to support this operation while preserving the intended local-edit safeguards; the recording runner used by the cluster tests bypasses this gate.
Why
Both the session-start pull and the console's pull button ran
git pull --ff-onlyinto whatever branch the checkout was on. A shared checkout that an agent had parked on its own branch never came back: every fast-forward failed, the failure was swallowed as offline degradation, the agent kept working on a month-old base, and the console showed that branch's HEAD as "the latest commit" while the button reported "Pull failed — the daemon may be offline" (the daemon was fine; it was refusing because the agent was mid-turn, and the CP mapped that CONFLICT to 503).What
Daemon —
syncWorkspaceRefreplacespullWorkspaceReffor the session-start sync (pullOnNewSession), the cluster volume path and the console request:git fetch <remote> +refs/heads/<branch>:refs/remotes/origin/<branch><branch>has commits the remote lacks — the branch is left alone and the detail says to push or move them (never discarded)git checkout --no-track -B <branch> refs/remotes/origin/<branch>— pins the local branch to the remote tip and checks it out; uncommitted edits ride along the waygit checkoutcarries them, and git refuses (naming the file) when one touches a file the sync would rewriteswitchedFromwhen the checkout was on another branchNever a merge, so local history cannot fork from the remote. The agent's own branches and their commits are untouched.
Control plane — the
gitpullroute maps a daemon refusal throughsendWorkspaceFailurelike every other console git write: "the agent is working in this workspace" is 409WORKSPACE_STALE; only an offline daemon is 503. Summary/description updated; path andoperationIdunchanged, wire contract unchanged.Web — the button is "Sync the checkout with its remote branch"; result copy is
Synced main — updated N files./Switched from X to main — …/Already in sync.; a status failure is explained with the shared git-write text ("The agent is working in this workspace right now. Try again when it is idle.").Tests
rawprobes (fetch + checkout, never a merge); cluster fake runner refuses oncheckout -Bfor the divergent-volume caseWORKSPACE_STALENot in scope
GitRunner.pullis no longer called by production code; the interface member is left for a separate cleanup.🤖 Generated with Claude Code