From 4f152af170cfab37b898ee041cc1c481161b9ebd Mon Sep 17 00:00:00 2001 From: Don Garrett Date: Fri, 2 Oct 2026 22:29:20 +0000 Subject: [PATCH] Flatten harness branch names so a model's branch can't block them Live run 3: the executor ran `git checkout -b ` itself, and publishing then failed, because git can't create '/milestone-1' while a branch named '' exists. Milestone and delegation branches are now '-milestone-N' and 'delegation-milestone-N-F'. D4's harness-owned branches (M2.1) remove the cause; this removes the collision on the current loop. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE --- nisse-dev/src/nisse_dev/executor.py | 2 +- nisse-dev/src/nisse_dev/publish.py | 6 ++- nisse-dev/tests/test_publish.py | 39 +++++++++++++------ nisse-dev/tests/test_quint_counterexamples.py | 4 +- 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/nisse-dev/src/nisse_dev/executor.py b/nisse-dev/src/nisse_dev/executor.py index d00676d..2ee0340 100644 --- a/nisse-dev/src/nisse_dev/executor.py +++ b/nisse-dev/src/nisse_dev/executor.py @@ -134,7 +134,7 @@ class LLMExecutor: result = self._delegate.run( transcript.as_actor("helper"), brief=brief, - branch=f"delegation/milestone-{milestone}-{failures}", + branch=f"delegation-milestone-{milestone}-{failures}", ) if result.usable: return None diff --git a/nisse-dev/src/nisse_dev/publish.py b/nisse-dev/src/nisse_dev/publish.py index ed8a72d..a23c3ee 100644 --- a/nisse-dev/src/nisse_dev/publish.py +++ b/nisse-dev/src/nisse_dev/publish.py @@ -55,8 +55,10 @@ class MilestonePublisher: def branch_for(self, task_id: str, milestone: int) -> str: # Namespaced by task so re-running a project against the same repo - # cannot collide with an earlier run's branch. - return f"{task_id}/milestone-{milestone}" + # cannot collide with an earlier run's branch. Flat, with no "/": + # git stores branch names as paths, so a branch named after the task + # (which a model did make, live) would block "/milestone-1". + return f"{task_id}-milestone-{milestone}" def publish( self, diff --git a/nisse-dev/tests/test_publish.py b/nisse-dev/tests/test_publish.py index 02ba4f5..5e744d2 100644 --- a/nisse-dev/tests/test_publish.py +++ b/nisse-dev/tests/test_publish.py @@ -124,14 +124,29 @@ def test_publish_commits_pushes_and_opens_a_pr( task_id="demo-abc", milestone=2, project="demo", plan="the plan" ) - assert git.pushed == ["demo-abc/milestone-2"] - assert client.opened[0]["head"] == "demo-abc/milestone-2" + assert git.pushed == ["demo-abc-milestone-2"] + assert client.opened[0]["head"] == "demo-abc-milestone-2" assert client.opened[0]["base"] == "main" assert "the plan" in client.opened[0]["body"] assert result.pull_request is not None assert "opened PR #1" in result.note +def test_a_branch_named_after_the_task_does_not_block_publishing( + store: StateStore, tmp_path: Path +) -> None: + # Live run 3: the executor ran `git checkout -b ` itself. With + # "/milestone-1" as the harness's branch, git refused it, since a + # branch name can't also be a directory of other branch names. + git = FakeGit(tmp_path / "work") + git.repo.checkout_new_branch("demo-abc") + result = build(store, git, FakeGitea()).publish( + task_id="demo-abc", milestone=1, project="demo", plan="p" + ) + assert result.pull_request is not None + assert git.pushed == ["demo-abc-milestone-1"] + + def test_intent_is_recorded_then_committed(store: StateStore, tmp_path: Path) -> None: build(store, FakeGit(tmp_path / "work"), FakeGitea()).publish( task_id="t", milestone=1, project="demo", plan="p" @@ -150,7 +165,7 @@ def test_failure_after_intent_leaves_it_pending_for_reconciliation( [pending] = store.pending_intents() assert pending.kind == OPEN_PR_INTENT - assert pending.payload["branch"] == "t/milestone-1" + assert pending.payload["branch"] == "t-milestone-1" assert pending.payload["sha"] @@ -171,7 +186,7 @@ def test_nothing_to_commit_is_reported_not_raised( def test_branch_is_namespaced_by_task(store: StateStore, tmp_path: Path) -> None: publisher = build(store, FakeGit(tmp_path / "work"), FakeGitea()) - assert publisher.branch_for("greet-9f2", 3) == "greet-9f2/milestone-3" + assert publisher.branch_for("greet-9f2", 3) == "greet-9f2-milestone-3" def test_reconciler_settles_an_intent_left_by_a_crash( @@ -183,9 +198,9 @@ def test_reconciler_settles_an_intent_left_by_a_crash( client = FakeGitea() publisher = build(store, FakeGit(tmp_path / "work"), client) intent_id = store.begin_intent( - OPEN_PR_INTENT, {"branch": "t/milestone-1"}, cause="crash simulation" + OPEN_PR_INTENT, {"branch": "t-milestone-1"}, cause="crash simulation" ) - client.existing.append("t/milestone-1") + client.existing.append("t-milestone-1") reconcilers = publisher.reconcilers() assert set(reconcilers) == {OPEN_PR_INTENT} @@ -205,8 +220,8 @@ def test_work_the_executor_committed_itself_is_published( task_id="t", milestone=1, project="demo", plan="p" ) assert result.pull_request is not None - assert git.pushed == ["t/milestone-1"] - assert git.remote["t/milestone-1"] == git.head_sha() + assert git.pushed == ["t-milestone-1"] + assert git.remote["t-milestone-1"] == git.head_sha() def test_a_followup_the_executor_committed_itself_is_pushed( @@ -219,7 +234,7 @@ def test_a_followup_the_executor_committed_itself_is_pushed( git.repo.commit_all("Address review") sha = publisher.push_followup(task_id="t", milestone=1, message="m") assert sha == git.head_sha() - assert git.pushed == ["t/milestone-1", "t/milestone-1"] + assert git.pushed == ["t-milestone-1", "t-milestone-1"] # Nothing new since that push: no second push, and it says so. assert publisher.push_followup(task_id="t", milestone=1, message="m") is None assert len(git.pushed) == 2 @@ -239,15 +254,15 @@ def test_publishing_again_adopts_the_open_pr_instead_of_opening_another( assert len(client.opened) == 1 assert result.pull_request is not None and result.pull_request.number == 1 assert "already open" in result.note - assert git.pushed == ["t/milestone-1", "t/milestone-1"] # updated it + assert git.pushed == ["t-milestone-1", "t-milestone-1"] # updated it assert store.pending_intents() == [] def test_a_closed_pr_is_not_adopted(store: StateStore, tmp_path: Path) -> None: git = FakeGit(tmp_path / "work") client = FakeGitea() - client.existing.append("t/milestone-1") - client.closed.add("t/milestone-1") + client.existing.append("t-milestone-1") + client.closed.add("t-milestone-1") publisher = build(store, git, client) assert publisher.published_pull_request("t", 1) is None publisher.publish(task_id="t", milestone=1, project="demo", plan="p") diff --git a/nisse-dev/tests/test_quint_counterexamples.py b/nisse-dev/tests/test_quint_counterexamples.py index 6ddc20a..59eb697 100644 --- a/nisse-dev/tests/test_quint_counterexamples.py +++ b/nisse-dev/tests/test_quint_counterexamples.py @@ -121,7 +121,7 @@ class Publisher: return {"open_pr": lambda intent: intent.payload["branch"] in self._gitea.prs} def publish(self, **kwargs: Any) -> PublishResult: - branch = f"{kwargs['task_id']}/milestone-{kwargs['milestone']}" + branch = f"{kwargs['task_id']}-milestone-{kwargs['milestone']}" self.attempts += 1 intent = self._store.begin_intent("open_pr", {"branch": branch}, cause="t") if branch in self._gitea.prs: @@ -135,7 +135,7 @@ class Publisher: ) def published_pull_request(self, task_id: str, milestone: int) -> Any: - branch = f"{task_id}/milestone-{milestone}" + branch = f"{task_id}-milestone-{milestone}" if branch in self._gitea.prs: return PullRequest(number=20, state="open", head=branch, url="u") return None