Skip to content

Let sub-agent dispatch run in an isolated git worktree - #293

Merged
TheGreatAxios merged 6 commits into
cl-4612-make-approval-prompts-scroll-when-content-overflows-thefrom
cl-4929-run-each-sub-agent-in-its-own-git-worktree-with-attributed
Aug 4, 2026
Merged

Let sub-agent dispatch run in an isolated git worktree#293
TheGreatAxios merged 6 commits into
cl-4612-make-approval-prompts-scroll-when-content-overflows-thefrom
cl-4929-run-each-sub-agent-in-its-own-git-worktree-with-attributed

Conversation

@TheGreatAxios

@TheGreatAxios TheGreatAxios commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Sub-agent dispatch can opt into worktree isolation: task() creates a fresh git worktree branched from the dispatcher's HEAD and runs the sub-agent's tools against it instead of the shared cwd.
  • The worktree lifecycle lives in src/subagent/worktree.ts: creation fails closed when the dispatcher cwd is not a git repository or git worktree add fails; cleanup removes the worktree if it is unchanged, or preserves it with a notice appended to the sub-agent's report if it has uncommitted changes.
  • cwd flows dispatch -> runSubAgent -> the sub-agent's identity context (identity-context.ts), which the permission gate already reads (from the approvals-lane stack) to attribute an approval prompt to the sub-agent's actual worktree path.
  • Isolation is opt-in via TaskToolDeps.useWorktree (propagated through nested/orchestrator dispatch); omitting it keeps today's shared-cwd behavior.

Verification

  • bun run typecheck
  • bun run build
  • bun test - 3597 pass, 0 fail

Closes CL-4929

@linear-code

linear-code Bot commented Aug 1, 2026

Copy link
Copy Markdown

CL-4929

@TheGreatAxios
TheGreatAxios force-pushed the cl-4929-run-each-sub-agent-in-its-own-git-worktree-with-attributed branch from fa1ef58 to 8fff41c Compare August 1, 2026 17:54
@TheGreatAxios
TheGreatAxios force-pushed the cl-4929-run-each-sub-agent-in-its-own-git-worktree-with-attributed branch from 8fff41c to 93deadd Compare August 2, 2026 04:09
@TheGreatAxios
TheGreatAxios force-pushed the cl-4929-run-each-sub-agent-in-its-own-git-worktree-with-attributed branch from 93deadd to 1c7d10a Compare August 2, 2026 04:38
@TheGreatAxios
TheGreatAxios force-pushed the cl-4929-run-each-sub-agent-in-its-own-git-worktree-with-attributed branch from 1c7d10a to aef59b5 Compare August 2, 2026 04:49
@TheGreatAxios
TheGreatAxios force-pushed the cl-4929-run-each-sub-agent-in-its-own-git-worktree-with-attributed branch from aef59b5 to 559efd4 Compare August 4, 2026 00:12
Sub-agents share the dispatcher's cwd today, so parallel workers
collide on the same working tree and the approval-prompt attribution
has nothing distinct to show. task() can now opt into worktree
isolation: each spawn gets a fresh worktree branched from the
dispatcher's HEAD, and its tools resolve against that path instead of
the parent cwd, so approval prompts (which already read the sub-agent
identity's cwd) surface the worktree path automatically. Worktree
setup fails closed when the dispatcher cwd is not a git repository or
worktree creation fails. On completion the worktree is removed if
unchanged; if the sub-agent left uncommitted changes, it is preserved
and a notice is appended to the returned report.
Cleanup checked git status --porcelain, which omits gitignored
files, so a sub-agent worktree holding only ignored output (dist/,
logs) looked clean and was deleted along with that output. Adding
--ignored makes dirty-detection catch it too.
The task tool description and the isolated-run comment both said the
sub-agent unconditionally shares your working tree, which is only
true in the default dispatch mode. Worktree isolation snapshots the
dispatcher's last commit, so uncommitted and untracked changes are
never visible to a sub-agent running in that mode.
git status never reports a git stash the sub-agent ran mid-task, so a
worktree holding one looked clean and got removed automatically — the
stash entry survives in the repo's shared refs/stash either way, but
went silently orphaned with no indication of which worktree it came
from. createSubAgentWorktree now captures the stash list as a baseline;
cleanup diffs the current list against it and preserves the worktree
with a notice naming the new stash entries instead of removing it.

Also preserves the caught error as "cause" on both WorktreeError throw
sites in this file, and collapses two stray double blank lines.
Record HEAD at create, treat a null stash baseline as unknown (preserve),
preserve when stash list fails at cleanup, and pass both through
finishWithWorktree so commits on detached HEAD are not left reflog-only.
@TheGreatAxios
TheGreatAxios force-pushed the cl-4929-run-each-sub-agent-in-its-own-git-worktree-with-attributed branch from 559efd4 to 967c4a5 Compare August 4, 2026 00:28
Sub-agent worktrees run with a different process cwd than the session that
built the gate. Relative path checks for auto-allow and restriction must
resolve against that process cwd so a cat of a worktree-local path is not
judged as if it opened under the session. Also refuse to collapse payloads
for bun, deno, busybox, ash, and osascript in the approval dialog.
@TheGreatAxios
TheGreatAxios marked this pull request as ready for review August 4, 2026 03:17
@TheGreatAxios
TheGreatAxios merged commit ee75800 into main Aug 4, 2026
1 check passed
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