Skip to content
Merged
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
8 changes: 6 additions & 2 deletions orchestrator/gateway_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1045,8 +1045,12 @@ def push_worktree_branch(
agent-targeted pipeline-push enforcement (#2028, #2051).

When ``ref`` is ``None`` (default), pushes the worktree's current
``HEAD`` — used when ``repo_path`` is a worktree checked out to
``branch``. On non-fast-forward rejection, performs
``HEAD`` via ``HEAD:refs/heads/{branch}`` — used when ``repo_path``
is a dedicated pipeline worktree. The worktree's ``HEAD`` may be on
``branch`` or detached (the per-slice BRC hook adds its worktree
with ``--detach``, #2778); the refspec names the destination
branch explicitly, so a branch checked out at ``repo_path`` is not
required. On non-fast-forward rejection, performs
``git fetch origin`` + ``git rebase origin/{branch}`` in the
worktree (with ``.egg-state/agent-outputs/`` auto-resolve) and
retries the push once.
Expand Down
26 changes: 17 additions & 9 deletions orchestrator/routes/pipelines.py
Original file line number Diff line number Diff line change
Expand Up @@ -10935,10 +10935,12 @@ def _commit_slice_brc_history_to_integration_branch(
messages from the message store; the staging directory is
scoped to this hook tick so concurrent slice hooks do not
cross-write each other (#2755).
2. Materialise a temporary git worktree on
2. Materialise a temporary **detached** git worktree on
``origin/<integration_branch>`` (the slice's integration branch).
``-B`` re-points the local branch ref so a prior tick that
crashed mid-flight can re-enter cleanly.
A detached worktree claims no branch ref, so it never collides
with the slice's own agent worktrees — which hold the
integration branch checked out for the duration of the slice
run — nor with a prior tick that crashed mid-flight (#2778).
3. Copy ONLY this slice's per-slice BRC files
(``<identifier>-implement-<slice_id>.{json,md}``) from the
staging directory to the integration worktree. Other slices'
Expand Down Expand Up @@ -11126,8 +11128,12 @@ def _commit_slice_brc_history_to_integration_branch(
*git_base,
"worktree",
"add",
"-B",
integration_branch,
# Detached, not ``-B <integration_branch>``: a branch
# can live in only one linked worktree, and the
# slice's agent worktrees already hold it — ``-B``
# lost that race with ``fatal: ... already used by
# worktree`` (#2778). See Step 2 in the docstring.
"--detach",
str(wt_path),
f"origin/{integration_branch}",
],
Expand Down Expand Up @@ -15717,10 +15723,12 @@ def _run_one_slice_inner(slice_id: str, parent_slice_id: str | None) -> tuple[in
# narrative without leaving the diff view.
#
# Best-effort: a failure here is logged and swallowed so
# the slice PR creation can still proceed — the BRC
# files remain on the work worktree as a fallback audit
# trail, and the next phase-boundary write will
# re-attempt them. Idempotent on retry.
# the slice PR creation can still proceed. There is no
# fallback surface — since #2758 the per-slice files
# live ONLY on the integration branch (committing them
# to ``work`` re-introduces the #2755 add/add conflict),
# so a failure here loses the slice's consensus
# transcript outright. Idempotent on retry.
if pipeline.repo:
try:
_commit_slice_brc_history_to_integration_branch(
Expand Down
51 changes: 51 additions & 0 deletions orchestrator/tests/test_per_slice_brc_commit.py
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,57 @@ def _selective_run(cmd, **kwargs):
assert ok is False
spawner.gateway.push_worktree_branch.assert_not_called()

def test_worktree_add_is_detached_not_branch_ref(self, tmp_path, pipeline, make_spawner):
"""Regression for #2778: the hook must add a *detached* worktree,
never ``-B <integration_branch>``.

The slice's own agent worktrees hold the integration branch
checked out for the duration of the slice run, and a branch can
live in only one linked worktree at a time. ``git worktree add
-B <branch>`` raced them and failed with ``fatal: '<branch>' is
already used by worktree at ...``, which the hook swallowed as a
best-effort no-op — silently dropping the slice PR's BRC
transcript. A detached worktree claims no ref, so it coexists
with the agent worktrees; the commit is pushed via
``HEAD:refs/heads/<branch>``.
"""
_seed_per_slice_brc_files(tmp_path, identifier=2548, slice_ids=["slice-1"])
spawner = make_spawner()
add_cmds: list[list[str]] = []

def _spy_run(cmd, **kwargs):
if "worktree" in cmd and "add" in cmd:
add_cmds.append(list(cmd))
result = MagicMock()
result.returncode = 0
result.stdout = ""
result.stderr = ""
return result

with (
patch("routes.pipelines._write_brc_history", _writer_stub(tmp_path)),
patch("subprocess.run", _spy_run),
):
_commit_slice_brc_history_to_integration_branch(
pipeline,
spawner,
tmp_path,
slice_id="slice-1",
integration_branch="egg/issue-2548/slice-1",
)

assert add_cmds, "the hook must run `git worktree add`"
for cmd in add_cmds:
assert "--detach" in cmd, f"worktree add must be detached (#2778); got: {cmd}"
assert "-B" not in cmd, (
"worktree add must not claim the integration branch ref with "
f"-B — it collides with the slice's agent worktrees (#2778); got: {cmd}"
)
assert "egg/issue-2548/slice-1" not in cmd, (
"the bare integration branch name must not be a `git worktree "
f"add` argument — only ``origin/<branch>`` as the commit-ish (#2778); got: {cmd}"
)

def test_push_raise_returns_false(self, tmp_path, pipeline, make_spawner):
"""If ``push_worktree_branch`` itself raises, swallow + False.
Any uncaught exception here would propagate up through
Expand Down
Loading