ISSUES CLOSED: #6841
8.0 KiB
Git Worktree Sandbox
The Git Worktree Sandbox provides isolated, reversible staging for LLM-generated
file changes during plan execution. It uses git worktree to create a detached
working tree on a dedicated branch, so modifications never touch the original branch
until the plan is explicitly applied.
Introduced in PR #5998 (merged 2026-04-09) as part of the M1 plan lifecycle implementation.
Overview
When a plan is executed against a git-checkout resource, CleverAgents creates a
temporary git worktree on a branch named cleveragents/plan-<plan_id>. The LLM
actor writes files into this worktree. On plan apply, the sandbox branch is merged
back into the project's current branch via git merge. If anything goes wrong, the
sandbox can be rolled back without affecting the original working tree.
Non-git projects fall back to the flat directory sandbox (shutil.copy2).
Lifecycle
create(plan_id)
│ Creates branch cleveragents/plan-<plan_id>
│ Creates worktree at /tmp/ca-sandbox-<plan_id>-<random>/
▼
get_path(resource_path)
│ Translates resource-relative paths to worktree paths
│ Actor writes files here
▼
commit(message)
│ git add -A → git commit → git merge <branch> (in original repo)
▼
cleanup()
│ git worktree remove --force
│ git branch -D cleveragents/plan-<plan_id>
│ git worktree prune
▼
CLEANED_UP
Rollback is available from ACTIVE or COMMITTED states:
rollback()
│ ACTIVE → git reset --hard <base_commit> + git clean -fd (in worktree)
│ COMMITTED → git reset --hard <pre_merge_commit> (in original repo)
│ + git reset --hard <base_commit> (in worktree)
▼
ROLLED_BACK
Status State Machine
| Status | Meaning |
|---|---|
PENDING |
Sandbox created but create() not yet called |
CREATED |
Worktree and branch exist; no writes yet |
ACTIVE |
Actor has written at least one file |
COMMITTED |
Changes committed and merged to original branch |
ROLLED_BACK |
Changes discarded; worktree reset to base commit |
ERRORED |
A git command failed; sandbox is unusable |
CLEANED_UP |
Worktree and branch removed |
Key Class: GitWorktreeSandbox
Module: cleveragents.infrastructure.sandbox.git_worktree
from cleveragents.infrastructure.sandbox.git_worktree import GitWorktreeSandbox
sandbox = GitWorktreeSandbox(
resource_id="01ABCDEF...", # ULID of the git-checkout resource
original_path="/path/to/repo",
git_timeout=30, # seconds per git command (default: 30)
)
# Create the worktree
ctx = sandbox.create(plan_id="01PLAN...")
print(ctx.sandbox_path) # /tmp/ca-sandbox-01PLAN-xxxx/
print(ctx.metadata["branch"]) # cleveragents/plan-01PLAN...
# Resolve a path inside the worktree
worktree_file = sandbox.get_path("src/calculator.py")
# After the actor writes files...
result = sandbox.commit("feat: implement calculator")
print(result.commit_ref) # git SHA of the sandbox commit
print(result.added_files) # ["src/calculator.py"]
# Cleanup
sandbox.cleanup()
Constructor Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
resource_id |
str |
— | ULID of the resource being sandboxed |
original_path |
str |
— | Absolute path to the git repository root |
git_timeout |
int |
30 |
Timeout in seconds for each git command |
Methods
create(plan_id: str) → SandboxContext
Creates the worktree and branch. Must be called before any other method.
- Verifies
original_pathis a git repository root (not a subdirectory). - Creates branch
cleveragents/plan-<sanitised_plan_id>. - Creates a temporary directory for the worktree.
- Returns a
SandboxContextwithsandbox_path,original_path,resource_id,plan_id,created_at, andmetadata(strategy, branch, original_branch, base_commit, worktree_path).
get_path(resource_path: str) → str
Translates a resource-relative path to an absolute path inside the worktree.
Raises ValueError on directory traversal attempts (.. components).
commit(message: str | None = None) → CommitResult
Stages all changes (git add -A), commits them in the worktree, then merges
the sandbox branch into the original branch (git merge --no-edit).
Returns a CommitResult with:
| Field | Type | Description |
|---|---|---|
sandbox_id |
str |
Sandbox ULID |
success |
bool |
True on success |
commit_ref |
str |
SHA of the sandbox commit |
changed_files |
list[str] |
Modified files |
added_files |
list[str] |
New files |
deleted_files |
list[str] |
Removed files |
error |
str | None |
Error message on failure |
timestamp |
datetime |
Commit timestamp |
If there are no staged changes, returns a CommitResult with success=True
and empty file lists (no-op commit).
rollback() → None
Discards all changes. Behaviour depends on current status:
ACTIVE: Resets the worktree branch tobase_commitand runsgit clean -fd.COMMITTED: Also resets the original branch topre_merge_commit(undoes the merge).
!!! warning "Multi-worktree safety"
Rolling back from COMMITTED executes git reset --hard on the original
branch. Other git worktrees or external processes tracking the same branch
will be affected.
cleanup() → None
Removes the worktree and deletes the sandbox branch. Idempotent — safe to call
multiple times. Falls back to shutil.rmtree if git worktree remove fails.
Error Types
All errors are defined in cleveragents.infrastructure.sandbox.protocol.
| Exception | When raised |
|---|---|
SandboxCreationError |
git worktree add or prerequisite git commands fail |
SandboxCommitError |
git commit or git merge fails |
SandboxRollbackError |
git reset or git clean fails |
SandboxStateError |
Method called in an invalid status |
Branch Naming
Branch names are sanitised to be git-safe:
- Only alphanumeric characters, hyphens, underscores, slashes, and dots are kept.
- Runs of disallowed characters are collapsed into a single hyphen.
- The final name is
cleveragents/plan-<sanitised_plan_id>.
Example: plan ID 01JQABC-xyz!foo → branch cleveragents/plan-01JQABC-xyz-foo.
Integration with Plan Lifecycle
The GitWorktreeSandbox is instantiated by the CLI plan execute command when the
plan's linked resource is a git-checkout type. The plan apply command calls
commit() followed by cleanup().
agents plan execute <plan_id>
→ GitWorktreeSandbox.create(plan_id)
→ LLM actor writes files via get_path()
agents plan apply <plan_id>
→ GitWorktreeSandbox.commit("cleveragents: apply plan <plan_id>")
→ GitWorktreeSandbox.cleanup()
→ Displays Apply Summary panel
The Apply Summary panel shows:
- Plan ID
- Artifact count (added + changed files)
- Insertions / deletions (from
git diff --stat) - Project name
- Applied-at timestamp
Gotchas
-
original_pathmust be the repository root, not a subdirectory. The sandbox verifies this withgit rev-parse --show-toplevel. -
Merge conflicts are not auto-resolved. If the original branch has diverged since the sandbox was created,
git mergemay fail. The sandbox transitions toERROREDand raisesSandboxCommitError. -
Cleanup is not automatic on error. Always call
cleanup()in afinallyblock or use a context manager wrapper to avoid orphaned worktrees. -
Git timeout defaults to 30 seconds. For large repositories or slow disks, increase
git_timeoutwhen constructing the sandbox.