Automatic Worktree: Why Helix Never Lets Agents Touch Main
Every coding agent eventually writes to your repository. The question is: what branch does it write to?
Most tools answer that question by making it your problem. Helix answers it at the system level, before the agent touches a single file.
The problem with writing directly to main
When an AI agent edits files, it needs somewhere to put them. The path of least resistance is the working directory you opened — which is usually your main branch.
This creates a class of problems that nobody talks about in agent demos:
- Partially completed tasks leave your main branch dirty. The agent started a refactor, got three files in, hit an error, and stopped. Now
git statusis a mess and you're not sure what's safe to commit. - Concurrent tasks collide. If you're running multiple agents or sessions simultaneously, they're all writing to the same checkout. File conflicts are unpredictable and hard to debug.
- Rollback is painful. The agent made changes you don't want. But it also made changes you do want, and they're all tangled in the same working copy.
- You lose the ability to review before merge. The code is already on main. The review is retroactive, not preventive.
The worktree problem is not unique to AI agents — it's a well-understood challenge in any parallel development workflow. Git's native answer is the worktree: a separate checkout of the repository, on a separate branch, in a separate directory. Changes stay isolated until you deliberately merge them.
The question is who creates and manages that worktree.
How other tools handle it
Several coding agent tools have added worktree support. The pattern is consistent across them:
User: enable worktree mode
Tool: ok, worktrees are now enabled
User: run task
Tool: creating worktree... done
User: review output
User: merge branch ← manual step
Worktree creation is optional. You have to know it exists and choose to use it. If you forget — or if you're using the tool for a quick task and don't think it's worth the overhead — the agent writes directly to your working copy.
Merge is always manual. The tool creates the worktree and does the work, but getting those changes back to main is your responsibility. This is fine for a single task. For five concurrent tasks, or for a workflow where you're doing frequent agent-assisted work, the manual merge overhead accumulates.
The tools have the right idea. But "opt-in worktree with manual merge" is still a workflow where the default path — not configuring it, running a quick task without thinking about it — leaves your main branch exposed.
Helix's approach: worktree as a system constraint
In Helix, worktree isolation is not a feature you enable. It is an architectural constraint built into how sessions interact with repositories.
The rules are enforced at the system level, not the prompt level:
-
No agent — not the Execution Agent, not a SubAgent — can write to a git-managed directory without first establishing a worktree binding. The system rejects writes to tracked files on the current branch without one.
-
Worktree binding is declared by the agent, not configured by the user. When an agent determines that a task requires writing to a repository, it calls
create_worktree_bindingbefore touching any files. The system creates the worktree, creates an isolated branch, and returns the path the agent should work in. -
Merge is automatic. When a session completes — after code review passes and changes are committed — the system merges the worktree branch to base, removes the worktree, and deletes the temporary branch. The user doesn't manage this.
Agent: I need to write to /projects/my-repo
Agent: create_worktree_binding(project_root="/projects/my-repo", task="add auth middleware")
System: created worktree at ~/.aiagent/worktree/{session}/{repo}
branch: aiagent/{session}/add-auth-middleware-a3f7c91d
base: main
Agent: [works entirely in worktree path]
Agent: [task complete, code review passed]
System: git add -A → git commit → git switch main → git merge --no-ff → git worktree remove → git branch -d
The agent never had access to main. Main is not dirty at any point during the task. The merge happened without user intervention.
What this looks like under the hood
When create_worktree_binding is called, the system:
- Resolves the Git repository root by walking up from the provided path (
git rev-parse --show-toplevel) - Reads the current branch — this becomes the merge target
- Generates a branch name from the session ID and task description:
aiagent/{sessionID}/{sanitized-task}-{8-char-hash} - Creates the worktree in a dedicated directory outside the repository:
~/.aiagent/worktree/{sessionID}/{repo-key}/ - Records the binding —
{project_root} → {worktree_path, branch, base_branch}— in the session state
From this point forward, every write the agent performs goes to the worktree path. The original checkout is untouched.
When the session finalizes:
finalizeWorktreeMergeAndCleanup:
git add -A (stage any unstaged changes)
git status --porcelain (check if commit is needed)
git commit -m "chore(worktree): ..."
git switch {base_branch}
git pull --ff-only (sync with remote before merge)
git merge --no-ff {branch} (preserve merge commit)
git worktree remove -f {path}
git branch -d {branch}
The merge is a proper non-fast-forward merge, so the branch history is preserved in the log. The worktree and temporary branch are cleaned up. The user's main branch has the changes; the session's working state is gone.
Multi-project and multi-repo tasks
A single session can write to multiple repositories simultaneously. Each repository gets its own worktree binding.
Agent: create_worktree_binding(project_root="/projects/backend", task="add grpc endpoint")
System: branch aiagent/{session}/add-grpc-endpoint-b2d1a4f8 in /projects/backend
worktree: ~/.aiagent/worktree/{session}/projects-backend/
Agent: create_worktree_binding(project_root="/projects/frontend", task="add grpc endpoint")
System: branch aiagent/{session}/add-grpc-endpoint-c9e3f271 in /projects/frontend
worktree: ~/.aiagent/worktree/{session}/projects-frontend/
The session state holds a map of project_root → {repo_path, worktree_path, branch, base_branch}. When the session finalizes, all bindings are merged and cleaned up in sequence.
This is what makes cross-repo tasks tractable. The agent can work across five repositories in a single session. None of them are dirty during the task. All of them get clean merge commits when the session completes.
Worktree and Manager Mode: the combination that scales
The worktree system becomes significantly more powerful when combined with Manager Mode's parallel SubAgent execution.
In Manager Mode, the Execution Agent can dispatch multiple SubAgents to run concurrently. Each SubAgent is a separate context with separate tool calls and separate LLM interactions. Without worktree isolation, parallel SubAgents writing to the same repository would collide immediately.
With worktree isolation, parallel SubAgents all work in the same worktree path — the one established by the Execution Agent's binding. They operate on the same isolated branch, which the Execution Agent coordinates.
┌─────────────────────────────────────────────────────────┐
│ Execution Agent │
│ │
│ create_worktree_binding("/projects/api", "...") │
│ → branch: aiagent/{session}/feature-x-a4c2d9e1 │
│ → worktree: ~/.aiagent/worktree/{session}/projects-api │
│ │
└──────┬───────────────┬────────────────┬─────────────────┘
│ run_subagent │ run_subagent │ run_subagent
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐
│ SubAgent A │ │ SubAgent B │ │ SubAgent C │
│ │ │ │ │ │
│ Writes to │ │ Writes to │ │ Writes to │
│ worktree │ │ worktree │ │ worktree │
│ (same path) │ │ (same path) │ │ (same path) │
└──────────────┘ └──────────────┘ └──────────────────────┘
│ │ │
└───────────────┴────────────────┘
│
Execution Agent collects results,
sequences writes, runs verification
│
finalizeSessionWorktree()
→ merge to main → cleanup
SubAgents cannot create their own worktree bindings — create_worktree_binding is filtered out of the SubAgent tool list at the system level. Only the Execution Agent controls worktree creation. SubAgents inherit the worktree context that was established before they were dispatched.
This means the Execution Agent is always the single point of coordination for repository state, even when ten SubAgents are running in parallel.
The code review gate
Worktree finalization — merge and cleanup — is gated on a code review pass. The session cannot complete and merge until WorktreeReviewDone is true.
func (m *Manager) finalizeSessionWorktree(session *ManagedSession) error {
if !session.WorktreeReviewDone {
return fmt.Errorf("worktree review is required before finalize")
}
// ... proceed with merge
}
This is not a prompt instruction asking the agent to review its work. It is a state machine check. If the review flag is not set, the finalize call returns an error. The merge does not happen.
In practice, the Execution Agent's workflow enforces this sequence:
1. think_about_whether_you_are_done()
2. start_code_review() ← sets WorktreeReviewDone when passed
3. summarize_changes()
4. merge worktree branch to main
Skipping code review is not possible without also skipping the merge. The two are linked at the system level.
Failure and cleanup
Worktree creation can partially succeed — the directory exists but the branch creation failed, or the session ended abnormally before finalization. The system handles this in two ways:
On explicit failure during finalization: The error is surfaced and the session is not marked complete. The worktree and branch remain intact. You can inspect the state, manually resolve the issue, and retry.
On cleanup without merge: If a session needs to be abandoned — because the task was cancelled, or because an error makes the changes unwanted — the system calls cleanupWorktreeBestEffort. This removes the worktree and force-deletes the branch, discarding the changes. It is a destructive operation, but it leaves the repository clean.
func (m *Manager) cleanupWorktreeBestEffort(info *subAgentWorktreeInfo) {
git worktree remove -f {worktreePath} // remove working directory
git branch -D {branch} // force-delete branch (unmerged)
}
The -D flag (force delete) is intentional: a branch being cleaned up has not been merged and would normally be protected by -d. The cleanup path is explicitly destructive because it represents discarded work.
Why automatic beats opt-in
The case for opt-in worktree is that it gives users control. You can run quick tasks without the overhead of worktree creation and cleanup.
The case against it: "quick tasks" is how most accidental main branch pollution starts. The task seemed quick. You didn't set up worktree. The agent did nine things you expected and one thing you didn't. Now you're untangling a mixed-up working copy.
Helix's position is that the overhead of worktree isolation is low enough — a git worktree add is fast, the cleanup is automatic — that the tradeoff is worth making unconditionally. The constraint removes an entire category of repository state problems from the surface area of things you need to think about.
You don't configure worktree isolation. You don't remember to turn it on. It is simply how Helix works.
What you get
In practice, Helix's automatic worktree system means:
- Your main branch is never dirty from agent work. Tasks in progress live on isolated branches in separate directories.
- Parallel sessions don't interfere. Each session has its own worktree, its own branch. Five sessions, five clean working copies.
- Merge is not a manual step. When a task completes and passes review, changes land on main automatically with a proper merge commit.
- History is clean and traceable. Every agent-driven change arrives as a distinct merge commit. Branch names encode session ID and task description. You can trace any change back to the session that produced it.
- Rollback is clear. If a session produces changes you don't want, the merge commit is a clean revert target. You don't have to untangle partial file edits.
Get started
Worktree isolation is enabled by default in every Helix session. There is nothing to configure.
When you open a session and run a task that writes to a repository, the agent handles worktree creation before writing. When the task completes, the merge and cleanup are handled automatically.
If you want to see the behavior directly:
- Open a Helix session in a workspace with a git repository
- Run any task that writes to files in that repository
- During execution, check
~/.aiagent/worktree/— you'll see the isolated working copy - When the task completes, the worktree is gone and the changes are on main
For complex tasks spanning multiple repositories or requiring parallel execution, combine with Manager Mode to get the full benefit of parallel SubAgents working safely within the same worktree boundary.
What's coming
We're continuing to improve the worktree workflow:
- Worktree inspection UI — view active worktrees, their branches, and pending changes directly from the session panel
- Selective merge — approve or reject individual commits from a session before they land on main
- Cross-session worktree sharing — let related sessions share a worktree boundary for coordinated multi-session work
- Conflict resolution tooling — better UI for the cases where automatic merge fails and manual resolution is needed
The core principle stays fixed: agents work on isolated branches, and main only receives deliberate, reviewed merges.
