Skip to content

feat(daemon): sync the workspace to its configured branch instead of pulling - #1829

Open
zfy0701 wants to merge 1 commit into
mainfrom
claude/workspace-sync
Open

feat(daemon): sync the workspace to its configured branch instead of pulling#1829
zfy0701 wants to merge 1 commit into
mainfrom
claude/workspace-sync

Conversation

@zfy0701

@zfy0701 zfy0701 commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Why

Both the session-start pull and the console's pull button ran git pull --ff-only into 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

DaemonsyncWorkspaceRef replaces pullWorkspaceRef for the session-start sync (pullOnNewSession), the cluster volume path and the console request:

  1. git fetch <remote> +refs/heads/<branch>:refs/remotes/origin/<branch>
  2. refuse if the local <branch> has commits the remote lacks — the branch is left alone and the detail says to push or move them (never discarded)
  3. 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 way git checkout carries them, and git refuses (naming the file) when one touches a file the sync would rewrite
  4. report files/insertions/deletions between the two HEADs, plus switchedFrom when the checkout was on another branch

Never a merge, so local history cannot fork from the remote. The agent's own branches and their commits are untouched.

Control plane — the gitpull route maps a daemon refusal through sendWorkspaceFailure like every other console git write: "the agent is working in this workspace" is 409 WORKSPACE_STALE; only an offline daemon is 503. Summary/description updated; path and operationId unchanged, 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

  • real-git: switching back from a parked branch carrying an uncommitted edit; refusing a local commit the remote lacks; refusing an overlapping edit (HEAD and the edit untouched)
  • workspace-git unit tests re-pinned to the sync's raw probes (fetch + checkout, never a merge); cluster fake runner refuses on checkout -B for the divergent-volume case
  • CP integration: the busy CONFLICT → 409 WORKSPACE_STALE

Not in scope

  • Surfacing a refused session-start sync to the model/console (today it is still swallowed as degradation; the Sync button reports it on demand). Follow-up.
  • GitRunner.pull is no longer called by production code; the interface member is left for a separate cleanup.

🤖 Generated with Claude Code

…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>

@agentconnect-md-test agentconnect-md-test Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant