BUG-HUNT: [correctness] GitWorktreeSandbox.commit() merges into whatever branch is currently checked out — original branch may have changed since sandbox creation, silently merging into wrong branch #6635

Open
opened 2026-04-09 22:38:01 +00:00 by HAL9000 · 0 comments
Owner

Bug Report: Correctness — commit() merges into current HEAD, not the recorded _original_branch

Severity Assessment

  • Impact: If the original repository's checked-out branch changes between create() and commit(), the sandbox changes are merged into the wrong branch. The plan's changes become unreachable on the intended branch and may corrupt an unrelated branch.
  • Likelihood: Medium — any external git operation on the original repo between sandbox creation and commit (e.g. a user running git checkout feature-x in the same repo, or another plan executor switching branches) triggers this bug
  • Priority: High

Location

  • File: src/cleveragents/infrastructure/sandbox/git_worktree.py
  • Function: GitWorktreeSandbox.commit
  • Lines: ~330–345

Description

During create(), the sandbox records the current branch as self._original_branch:

# create() — records the branch at sandbox creation time
result = _run_git(
    ["rev-parse", "--abbrev-ref", "HEAD"],
    cwd=self._original_path,
    timeout=self._git_timeout,
)
self._original_branch = result.stdout.strip()

However, commit() never checks out or confirms self._original_branch before merging. It simply runs git merge in the original repository's working directory, which will merge into whatever branch is currently checked out there:

# commit() — merges into the CURRENT branch, ignoring _original_branch
_run_git(
    ["merge", self._branch_name, "--no-edit"],
    cwd=self._original_path,      # ← HEAD of original_path could be any branch now
    timeout=self._git_timeout,
)

Evidence

Sequence of events that triggers the bug:

  1. sandbox.create("plan-001") — original repo is on main. Records _original_branch = "main".
  2. A user (or another process) runs git checkout dev in the original repo.
  3. sandbox.commit("changes") — merges into dev (current HEAD), not main.
  4. main never receives the plan's changes.
  5. dev receives unexpected changes from an unrelated plan.

This is particularly dangerous when:

  • Multiple plans run concurrently and one switches branches
  • The original repo is a shared workspace checked out by a human user
  • The SandboxManager creates a sandbox for main but the user has since switched to a feature branch

Expected Behavior

commit() should verify that the currently checked-out branch in self._original_path is still self._original_branch, and either:
(a) Raise SandboxCommitError if the branch has changed, prompting the user to re-create the sandbox, OR
(b) Temporarily switch back to _original_branch before merging (then restore current branch).

Actual Behavior

commit() unconditionally merges into whatever branch is currently checked out. _original_branch is recorded but never validated or used during the merge.

Suggested Fix

Add a branch guard at the start of the merge step:

# Verify the original branch hasn't changed
current_branch_result = _run_git(
    ["rev-parse", "--abbrev-ref", "HEAD"],
    cwd=self._original_path,
    timeout=self._git_timeout,
)
current_branch = current_branch_result.stdout.strip()
if current_branch != self._original_branch:
    raise SandboxCommitError(
        f"Cannot commit sandbox {self._sandbox_id}: original repository is now "
        f"on branch '{current_branch}', expected '{self._original_branch}'. "
        "Re-create the sandbox or switch back to the original branch first."
    )

# Now safe to merge
_run_git(
    ["merge", self._branch_name, "--no-edit"],
    cwd=self._original_path,
    timeout=self._git_timeout,
)

Category

correctness / concurrency

TDD Note

After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. The test will use tags: @tdd_issue, @tdd_issue_<this-issue-number>, and @tdd_expected_fail to prove the bug exists before fixing it.


Automated by CleverAgents Bot
Supervisor: Bug Hunting | Agent: bug-hunter

## Bug Report: Correctness — `commit()` merges into current HEAD, not the recorded `_original_branch` ### Severity Assessment - **Impact**: If the original repository's checked-out branch changes between `create()` and `commit()`, the sandbox changes are merged into the wrong branch. The plan's changes become unreachable on the intended branch and may corrupt an unrelated branch. - **Likelihood**: Medium — any external git operation on the original repo between sandbox creation and commit (e.g. a user running `git checkout feature-x` in the same repo, or another plan executor switching branches) triggers this bug - **Priority**: High ### Location - **File**: `src/cleveragents/infrastructure/sandbox/git_worktree.py` - **Function**: `GitWorktreeSandbox.commit` - **Lines**: ~330–345 ### Description During `create()`, the sandbox records the current branch as `self._original_branch`: ```python # create() — records the branch at sandbox creation time result = _run_git( ["rev-parse", "--abbrev-ref", "HEAD"], cwd=self._original_path, timeout=self._git_timeout, ) self._original_branch = result.stdout.strip() ``` However, `commit()` never checks out or confirms `self._original_branch` before merging. It simply runs `git merge` in the original repository's working directory, which will merge into whatever branch is currently checked out there: ```python # commit() — merges into the CURRENT branch, ignoring _original_branch _run_git( ["merge", self._branch_name, "--no-edit"], cwd=self._original_path, # ← HEAD of original_path could be any branch now timeout=self._git_timeout, ) ``` ### Evidence **Sequence of events that triggers the bug**: 1. `sandbox.create("plan-001")` — original repo is on `main`. Records `_original_branch = "main"`. 2. A user (or another process) runs `git checkout dev` in the original repo. 3. `sandbox.commit("changes")` — merges into `dev` (current HEAD), not `main`. 4. `main` never receives the plan's changes. 5. `dev` receives unexpected changes from an unrelated plan. This is particularly dangerous when: - Multiple plans run concurrently and one switches branches - The original repo is a shared workspace checked out by a human user - The `SandboxManager` creates a sandbox for `main` but the user has since switched to a feature branch ### Expected Behavior `commit()` should verify that the currently checked-out branch in `self._original_path` is still `self._original_branch`, and either: (a) Raise `SandboxCommitError` if the branch has changed, prompting the user to re-create the sandbox, OR (b) Temporarily switch back to `_original_branch` before merging (then restore current branch). ### Actual Behavior `commit()` unconditionally merges into whatever branch is currently checked out. `_original_branch` is recorded but never validated or used during the merge. ### Suggested Fix Add a branch guard at the start of the merge step: ```python # Verify the original branch hasn't changed current_branch_result = _run_git( ["rev-parse", "--abbrev-ref", "HEAD"], cwd=self._original_path, timeout=self._git_timeout, ) current_branch = current_branch_result.stdout.strip() if current_branch != self._original_branch: raise SandboxCommitError( f"Cannot commit sandbox {self._sandbox_id}: original repository is now " f"on branch '{current_branch}', expected '{self._original_branch}'. " "Re-create the sandbox or switch back to the original branch first." ) # Now safe to merge _run_git( ["merge", self._branch_name, "--no-edit"], cwd=self._original_path, timeout=self._git_timeout, ) ``` ### Category correctness / concurrency ### TDD Note After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. The test will use tags: `@tdd_issue`, `@tdd_issue_<this-issue-number>`, and `@tdd_expected_fail` to prove the bug exists before fixing it. --- **Automated by CleverAgents Bot** Supervisor: Bug Hunting | Agent: bug-hunter
HAL9000 added this to the v3.2.0 milestone 2026-04-09 22:47:15 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core#6635
No description provided.