# Git Worktree Sandbox **Package:** `cleveragents.infrastructure.sandbox.git_worktree` **Introduced:** Unreleased (issue #4454) The git worktree sandbox isolates plan modifications in a dedicated git branch and worktree. On commit, changes are merged back to the original branch via `git merge`. On rollback, the worktree is discarded entirely. This replaces the previous flat `shutil.copy2` apply strategy for git-checkout resources. --- ## Purpose When a plan applies changes to a git-checkout resource, the changes must be isolated from the working tree until the user approves them. The git worktree sandbox achieves this by: 1. Creating a new branch `cleveragents/plan-` from the current HEAD. 2. Creating a temporary git worktree at a system temp directory. 3. Letting the actor write changes inside the worktree. 4. On commit: staging all changes, committing them in the worktree, then merging the sandbox branch back into the original branch. 5. On rollback: resetting the worktree (and optionally the original branch) to the base commit and discarding the worktree. Non-git projects fall back to the original flat file copy strategy. --- ## `GitWorktreeSandbox` ```python from cleveragents.infrastructure.sandbox.git_worktree import GitWorktreeSandbox sandbox = GitWorktreeSandbox( resource_id="01HZ...", original_path="/path/to/repo", ) ``` Implements the `Sandbox` protocol. **Constructor parameters:** | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `resource_id` | `str` | — | Identifier of the resource being sandboxed | | `original_path` | `str` | — | Path to the git repository root | | `git_timeout` | `int` | `30` | Timeout in seconds for git commands | --- ## Lifecycle ``` PENDING ──► create() ──► CREATED ──► get_path() ──► ACTIVE │ commit() ─┤─► COMMITTED ──► cleanup() ──► CLEANED_UP │ rollback() ─┴─► ROLLED_BACK ──► cleanup() ──► CLEANED_UP ``` ### `create(plan_id) → SandboxContext` Creates the worktree and branch. ```python ctx = sandbox.create(plan_id="plan-01HZ...") # ctx.sandbox_path — path to the worktree # ctx.metadata["branch"] — e.g. "cleveragents/plan-plan-01HZ..." # ctx.metadata["base_commit"] — HEAD SHA at creation time ``` Raises `SandboxCreationError` if the path is not a git repository root or if `git worktree add` fails. ### `get_path(resource_path) → str` Translates a resource-relative path to an absolute path inside the worktree. Rejects `..` path traversal attempts. ```python worktree_file = sandbox.get_path("src/main.py") # → "/tmp/ca-sandbox-plan-01HZ.../src/main.py" ``` ### `commit(message=None) → CommitResult` Stages all changes in the worktree, commits them, then merges the sandbox branch into the original branch. ```python result = sandbox.commit("feat: implement new feature") # result.success — True on success # result.commit_ref — SHA of the sandbox commit # result.changed_files — list of modified file paths # result.added_files — list of newly added file paths # result.deleted_files — list of deleted file paths ``` If there are no staged changes, returns a `CommitResult` with empty file lists and no new commit. Raises `SandboxCommitError` if the commit or merge fails. ### `rollback()` Discards all worktree changes. - From `ACTIVE`: resets the worktree branch to the base commit. - From `COMMITTED`: also resets the original branch to the pre-merge commit (fully undoes the merge). !!! warning "Multi-worktree safety" Rolling back from `COMMITTED` executes `git reset --hard` on the original branch. If other git worktrees or external processes are tracking the same branch, they will be affected. ### `cleanup()` Removes the worktree directory and deletes the sandbox branch. Idempotent — safe to call multiple times. --- ## Apply Summary When `plan apply` uses the git worktree strategy, the CLI displays a spec-aligned Apply Summary: ``` ╭─ Apply Summary ──────────────────────────────────────╮ │ Plan ID: plan-01HZ... │ │ Artifacts: 3 files changed │ │ Insertions: +142 │ │ Deletions: -17 │ │ Project: local/my-project │ │ Timestamp: 2026-04-09 17:30:00 │ ╰──────────────────────────────────────────────────────╯ ╭─ Sandbox Cleanup ────────────────────────────────────╮ │ ✓ Worktree removed │ │ ✓ Branch deleted │ ╰──────────────────────────────────────────────────────╯ ✓ OK Changes applied ``` --- ## Error Types | Exception | When Raised | |-----------|-------------| | `SandboxCreationError` | `git worktree add` fails or path is not a git root | | `SandboxCommitError` | `git commit` or `git merge` fails | | `SandboxRollbackError` | `git reset --hard` fails during rollback | | `SandboxStateError` | Method called in an invalid lifecycle state | All exceptions are importable from `cleveragents.infrastructure.sandbox.protocol`. --- ## Branch Naming Branch names are sanitised from the plan ID: - Only alphanumeric characters, hyphens, underscores, slashes, and dots are kept. - Runs of disallowed characters are collapsed into a single hyphen. - The final branch name has the form `cleveragents/plan-`. --- ## Fallback for Non-Git Projects `PlanLifecycleService` detects whether the resource is a git-checkout type. If not, it falls back to the original flat file copy strategy (`shutil.copy2`). The git worktree sandbox is only used for resources where `resource_type_name` is `git-checkout` or `git`. --- ## Related - [Architecture — Plan Lifecycle](../architecture.md#plan-lifecycle) - [ADR-015 Sandbox & Checkpoint](../adr/ADR-015-sandbox-and-checkpoint.md) - [ADR-038 Cross-Mechanism Sandbox Coordination](../adr/ADR-038-cross-mechanism-sandbox-coordination.md) - [`cleveragents.infrastructure.sandbox`](../api/core.md)