Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .cursor/skills/record-agent-issue/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,22 @@ Future triage moves conclusively fixed, non-reproducible, or superseded records
to `docs/issues/resolved/` and updates their resolution history. Do not delete
records.

## Shared worktree safety

Concurrent Cursor agents (or moving the agent root between sibling clones) can
silently change which branch is checked out in a shared worktree. Before any
mutating git command (`cherry-pick`, `commit`, `push`, `checkout` that writes),
assert the expected branch:

```bash
uv run python -m devops.git.assert_branch <expected-branch> --repo <path> \
--operation "git cherry-pick origin/results"
```

Prefer separate worktrees per concurrent agent when possible. When a branch
mismatch error fires, re-check `git status` and the agent UI root before
retrying the git operation.

## Common gotchas

- `run_manifest.json` is per-run provenance, not the cross-cutting issue queue.
Expand Down
6 changes: 5 additions & 1 deletion .cursor/skills/vast-provisioning/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ run a command in `tmux`.
> no boxes remain (`status`, or check <https://console.vast.ai/instances/>).

> **DO NOT TAKE OVER ANOTHER AGENT'S BOX:** multiple Cursor agents (or git
> worktrees) can run on the same Mac against the same vast.ai account. Each
> worktrees) can run on the same Mac against the same vast.ai account. The same
> sharing also applies to **git branches**: assert the expected branch with
> `uv run python -m devops.git.assert_branch <branch> --repo <path>` before
> `git commit`, `git push`, or result-import commands so a concurrent session
> cannot land writes on the wrong branch. Each
> library checkout has its **own** gitignored `devops/vast/state.json`, but
> `~/.ssh/config.d/vast.conf` and the vast account are **shared machine-wide**.
> An agent whose `state.json` is empty must **not** assume no boxes are running.
Expand Down
5 changes: 5 additions & 0 deletions devops/git/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Git helpers for local agent orchestration (not used on training boxes)."""

from devops.git.assert_branch import assert_branch, current_branch, BranchMismatchError

__all__ = ["assert_branch", "current_branch", "BranchMismatchError"]
108 changes: 108 additions & 0 deletions devops/git/assert_branch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
"""Assert the active Git branch before mutating operations in shared worktrees.

Concurrent Cursor agent sessions can mutate one worktree's HEAD when they share
a checkout or when the agent root moves between sibling clones. Call
``assert_branch`` (or the CLI) immediately before ``git cherry-pick``, ``git
commit``, ``git push``, and similar write operations.
"""

from __future__ import annotations

import argparse
import subprocess
import sys
from pathlib import Path


class BranchMismatchError(Exception):
"""Raised when the active branch does not match the expected branch."""

def __init__(
self,
repo: Path,
expected: str,
actual: str,
*,
operation: str | None = None,
) -> None:
self.repo = repo
self.expected = expected
self.actual = actual
self.operation = operation
op = f" before {operation}" if operation else ""
super().__init__(
f"expected branch {expected!r} in {repo}{op}, but HEAD is {actual!r}"
)


def current_branch(repo: Path) -> str:
"""Return the abbreviated name of the active branch in ``repo``."""
completed = subprocess.run(
["git", "rev-parse", "--abbrev-ref", "HEAD"],
cwd=repo,
capture_output=True,
text=True,
check=False,
)
if completed.returncode != 0:
detail = (completed.stderr or completed.stdout).strip()
raise RuntimeError(f"git rev-parse failed in {repo}: {detail}")
branch = completed.stdout.strip()
if not branch or branch == "HEAD":
raise RuntimeError(
f"detached HEAD in {repo}; branch assertion requires a named branch"
)
return branch


def assert_branch(
repo: Path,
expected: str,
*,
operation: str | None = None,
) -> str:
"""Return the active branch when it matches ``expected``; otherwise raise."""
actual = current_branch(repo)
if actual != expected:
raise BranchMismatchError(repo, expected, actual, operation=operation)
return actual


def _build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Assert the active Git branch before a mutating git operation.",
)
parser.add_argument(
"expected_branch",
help="Branch name that must be checked out in the target repository.",
)
parser.add_argument(
"--repo",
type=Path,
default=Path.cwd(),
help="Repository path (default: current working directory).",
)
parser.add_argument(
"--operation",
default=None,
help="Optional label for the upcoming git command (included in errors).",
)
return parser


def main(argv: list[str] | None = None) -> int:
args = _build_parser().parse_args(argv)
repo = args.repo.resolve()
try:
assert_branch(repo, args.expected_branch, operation=args.operation)
except BranchMismatchError as exc:
print(str(exc), file=sys.stderr)
return 1
except RuntimeError as exc:
print(str(exc), file=sys.stderr)
return 2
return 0


if __name__ == "__main__":
sys.exit(main())
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
status: open
status: resolved
severity: high
area: agent orchestration / git worktree isolation
discovered: 2026-07-19
Expand Down Expand Up @@ -55,3 +55,9 @@ the risk.
- 2026-07-19 — Recorded from the MESS3 result-import incident.
- 2026-07-19 — Reproduced during issue reporting: moving the agent root to the
harness clone silently checked out the experiment repository's branch name.
- 2026-08-02 — Fix drafted on `automated/bugfix/2026-08-02-shared-worktree-branch-race`
(not merged).
- 2026-08-09 — Weekly triage re-landed `devops.git.assert_branch` CLI/helper and
agent-skill guidance so mutating git commands can fail fast when HEAD drifted.
Cursor platform behavior is unchanged; separate worktrees per concurrent agent
remains the strongest isolation. Covered by `tests/test_git_assert_branch.py`.
76 changes: 76 additions & 0 deletions tests/test_git_assert_branch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import subprocess
from pathlib import Path

import pytest

from devops.git.assert_branch import (
BranchMismatchError,
assert_branch,
current_branch,
main,
)


def _init_repo(path: Path) -> None:
subprocess.run(["git", "init", "-b", "main"], cwd=path, check=True, capture_output=True)
subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=path, check=True)
subprocess.run(["git", "config", "user.name", "test"], cwd=path, check=True)
(path / "README").write_text("seed\n")
subprocess.run(["git", "add", "README"], cwd=path, check=True, capture_output=True)
subprocess.run(
["git", "commit", "-m", "seed"],
cwd=path,
check=True,
capture_output=True,
)


def test_current_branch_returns_active_branch(tmp_path: Path) -> None:
repo = tmp_path / "repo"
repo.mkdir()
_init_repo(repo)
assert current_branch(repo) == "main"


def test_assert_branch_passes_when_branch_matches(tmp_path: Path) -> None:
repo = tmp_path / "repo"
repo.mkdir()
_init_repo(repo)
assert assert_branch(repo, "main") == "main"


def test_assert_branch_raises_on_mismatch(tmp_path: Path) -> None:
repo = tmp_path / "repo"
repo.mkdir()
_init_repo(repo)
subprocess.run(
["git", "checkout", "-b", "feature"],
cwd=repo,
check=True,
capture_output=True,
)
with pytest.raises(BranchMismatchError) as excinfo:
assert_branch(repo, "main", operation="git cherry-pick")
assert excinfo.value.expected == "main"
assert excinfo.value.actual == "feature"
assert excinfo.value.operation == "git cherry-pick"


def test_cli_returns_nonzero_on_mismatch(tmp_path: Path) -> None:
repo = tmp_path / "repo"
repo.mkdir()
_init_repo(repo)
subprocess.run(
["git", "checkout", "-b", "feature"],
cwd=repo,
check=True,
capture_output=True,
)
assert main(["main", "--repo", str(repo)]) == 1


def test_cli_succeeds_when_branch_matches(tmp_path: Path) -> None:
repo = tmp_path / "repo"
repo.mkdir()
_init_repo(repo)
assert main(["main", "--repo", str(repo)]) == 0