Files
cleveragents-core/docs/modules/git-worktree-sandbox.md
T
HAL9000 a182233f6a docs(modules): add git worktree sandbox module documentation
Comprehensive module reference for GitWorktreeSandbox (v3.5.0, PR #5998):
- Lifecycle state machine diagram with complete state transitions
- Key classes: GitWorktreeSandbox, SandboxContext, CommitResult, SandboxStatus
- Branch naming convention with sanitisation examples
- Error handling table including ValueError for git_timeout <= 0
- Rollback behaviour (pre-commit vs post-commit)
- Integration with PlanLifecycleService pseudocode
- Apply Summary output format
- Testing guidance referencing actual BDD feature files:
  features/git_worktree_sandbox.feature
  features/git_worktree_apply.feature
  features/git_worktree_coverage_boost.feature
- Updated CHANGELOG.md with entry under [Unreleased] ### Added
- Updated CONTRIBUTORS.md with documentation contribution record

ISSUES CLOSED: #8051
2026-04-28 09:25:47 +00:00

10 KiB

Git Worktree Sandbox Module

Package: cleveragents.infrastructure.sandbox Introduced: v3.5.0 (PR #5998)

The git worktree sandbox provides isolated, branch-based staging for plan modifications against git-checkout resources. Changes are committed to a dedicated branch inside a temporary worktree and merged back to the original branch only when the plan is applied — leaving the working tree untouched until the operator explicitly approves.

For the sandbox protocol interface, see docs/api/core.md. For the plan lifecycle that invokes the sandbox, see docs/architecture.md.

A git worktree is a linked working tree that shares the same git object database as the main repository but has its own checked-out branch and working directory. This means:

  • Isolation: LLM-generated changes are committed to a dedicated branch (cleveragents/plan-<plan_id>) without touching the main working tree.
  • Auditability: Every plan execution produces a real git commit with a diff you can inspect before merging.
  • Conflict detection: git merge surfaces conflicts that flat file copy would silently overwrite.
  • Rollback: The worktree branch can be deleted without affecting the main branch.

When a plan executes against a git-checkout resource, the LLM writes files to an isolated git worktree rather than directly to the project directory. This guarantees that:

  • The original working tree is never modified until plan apply is run.
  • Rollback is a single git reset --hard — no file-by-file undo.
  • The diff between the base commit and the sandbox branch is always available via plan diff.
  • Concurrent plans on the same repository do not interfere with each other.

Non-git projects fall back to the original flat shutil.copy2 strategy.


Lifecycle

GitWorktreeSandbox(resource_id, original_path)
    │
    ▼
sandbox.create(plan_id)        → CREATED
    │  Creates branch cleveragents/plan-<plan_id>
    │  Creates temporary worktree at /tmp/ca-sandbox-<plan_id>-*/
    │
    ▼
sandbox.get_path("src/x.py")   → ACTIVE
    │  Translates resource-relative path to worktree-absolute path
    │  Actor writes files here
    │
    ▼
sandbox.commit("message")      → COMMITTED
    │  git add -A && git commit in worktree
    │  git merge <branch> into original branch
    │
    ▼
sandbox.cleanup()              → CLEANED_UP
       git worktree remove --force
       git branch -D cleveragents/plan-<plan_id>
       git worktree prune

At any point before commit(), calling rollback() resets the worktree to the base commit and transitions to ROLLED_BACK. After commit(), rollback() also resets the original branch to the pre-merge commit, fully undoing the merge.


Key Classes

GitWorktreeSandbox

from cleveragents.infrastructure.sandbox.git_worktree import GitWorktreeSandbox

sandbox = GitWorktreeSandbox(
    resource_id="my-repo",
    original_path="/path/to/project",
    git_timeout=30,          # optional; default 30 s
)
ctx = sandbox.create(plan_id="01JXYZ")
worktree_file = sandbox.get_path("src/main.py")
# ... actor writes to worktree_file ...
result = sandbox.commit("feat: implement feature X")
sandbox.cleanup()
Parameter Type Description
resource_id str Identifier of the resource being sandboxed
original_path str Absolute path to the git repository root
git_timeout int Timeout in seconds for each git command (default: 30)

SandboxContext

Returned by create(). Contains:

Attribute Type Description
sandbox_id str ULID uniquely identifying this sandbox instance
sandbox_path str Absolute path to the worktree directory
original_path str Absolute path to the original repository
resource_id str Resource being sandboxed
plan_id str Plan that owns this sandbox
created_at datetime Creation timestamp
metadata dict Extra info: strategy, branch, original_branch, base_commit, worktree_path

CommitResult

Returned by commit(). Contains:

Attribute Type Description
sandbox_id str Sandbox identifier
success bool Whether the commit and merge succeeded
commit_ref str | None SHA of the commit in the worktree branch
changed_files list[str] Modified files
added_files list[str] New files
deleted_files list[str] Deleted files
error str | None Error message if success=False
timestamp datetime Commit timestamp

SandboxStatus

Lifecycle state machine:

PENDING → CREATED → ACTIVE → COMMITTED → CLEANED_UP
                  ↘ ROLLED_BACK ↗
                  ↘ ERRORED
  1. The worktree is removed: git worktree remove <tmp_dir> --force.
  2. The worktree branch is deleted: git branch -d cleveragents/plan-<plan_id>.

Branch Naming

The sandbox branch is named cleveragents/plan-<sanitised_plan_id>. The sanitiser replaces any character outside [a-zA-Z0-9/_.-] with a hyphen and strips leading/trailing hyphens. For example:

Plan ID Branch
01JXYZ cleveragents/plan-01JXYZ
plan with spaces cleveragents/plan-plan-with-spaces
# Review the conflict details
agents plan errors <plan_id>

## Error Handling

| 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 |
| `ValueError` | Empty `resource_id`, `original_path`, or `plan_id`; path traversal attempt; `git_timeout <= 0` |

All git commands are wrapped with a configurable timeout.
`subprocess.TimeoutExpired` is caught and re-raised as the appropriate
`Sandbox*Error`.

## Context Hydration

## Rollback Behaviour

### Before `commit()` (ACTIVE → ROLLED_BACK)

Resets the worktree branch to the base commit and runs `git clean -fd` to
remove untracked files.  The original branch is untouched.

### After `commit()` (COMMITTED → ROLLED_BACK)

1. Resets the **original branch** to the pre-merge commit (`git reset --hard <pre_merge_sha>`), undoing the merge.
2. Resets the **worktree branch** to the base commit.

> **Warning:** Rolling back from `COMMITTED` executes `git reset --hard`
> on the original branch.  If other git worktrees or external processes
> track the same branch, they will be affected.

---

## Integration with Plan Lifecycle

`PlanLifecycleService` creates a `GitWorktreeSandbox` automatically when
the plan's linked resource is a `git-checkout` type:

```python
# Pseudocode — see PlanLifecycleService.execute_plan()
if resource.type == "git-checkout":
    sandbox = GitWorktreeSandbox(resource.id, resource.path)
    ctx = sandbox.create(plan.id)
    # Actor writes to ctx.sandbox_path
    result = sandbox.commit(f"plan({plan.id}): apply changes")
    sandbox.cleanup()

The Apply Summary printed to the terminal includes:

  • Plan ID
  • Artifacts (added/changed/deleted file counts)
  • Insertions and deletions (from git diff --stat)
  • Project name and timestamp
  • Sandbox cleanup confirmation
  • ✓ OK Changes applied footer

Apply Summary Output

╭─ Apply Summary ──────────────────────────────────────────────────────────╮
│ Plan ID   : 01JXYZ                                                        │
│ Project   : my-project                                                    │
│ Timestamp : 2026-04-09 14:33:34                                           │
│ Artifacts : 3 added, 2 changed, 0 deleted                                 │
│ Diff      : +127 / -43                                                    │
╰───────────────────────────────────────────────────────────────────────────╯

╭─ Sandbox Cleanup ─────────────────────────────────────────────────────────╮
│ ✓ Worktree removed                                                        │
│ ✓ Branch cleveragents/plan-01JXYZ deleted                                 │
╰───────────────────────────────────────────────────────────────────────────╯

✓ OK  Changes applied

Testing

The git worktree sandbox is covered by BDD scenarios in the following Behave feature files:

  • features/git_worktree_sandbox.feature — core lifecycle, path resolution, commit, rollback, cleanup, and error handling
  • features/git_worktree_apply.feature — end-to-end apply flow via PlanLifecycleService
  • features/git_worktree_coverage_boost.feature — additional edge-case coverage for branch naming, timeout handling, and concurrent sandboxes

Example scenario from features/git_worktree_sandbox.feature:

Scenario: Create a git worktree sandbox
  When a gwt sandbox is created for plan "plan-001"
  Then the gwt sandbox should be in the "created" state
  And the gwt sandbox context should reference plan "plan-001"
  And the gwt sandbox context should have strategy metadata "git_worktree"
  And the gwt sandbox worktree path should exist

Run the BDD suite with:

nox -s unit_tests