Files
temp/docs/modules/git-worktree-sandbox.md
T

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_path is a git repository root (not a subdirectory).
  • Creates branch cleveragents/plan-<sanitised_plan_id>.
  • Creates a temporary directory for the worktree.
  • Returns a SandboxContext with sandbox_path, original_path, resource_id, plan_id, created_at, and metadata (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 to base_commit and runs git clean -fd.
  • COMMITTED: Also resets the original branch to pre_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

  1. original_path must be the repository root, not a subdirectory. The sandbox verifies this with git rev-parse --show-toplevel.

  2. Merge conflicts are not auto-resolved. If the original branch has diverged since the sandbox was created, git merge may fail. The sandbox transitions to ERRORED and raises SandboxCommitError.

  3. Cleanup is not automatic on error. Always call cleanup() in a finally block or use a context manager wrapper to avoid orphaned worktrees.

  4. Git timeout defaults to 30 seconds. For large repositories or slow disks, increase git_timeout when constructing the sandbox.