Found by codex during the #685 bot gate (review comment). Filed rather than fixed in #685: it belongs to the carry-obligation family rather than the merge-failure taxonomy that PR is about, and that PR is already carrying six rounds of fixes.
Claim
WorktreeFlow._protected_carry_paths (src/bmad_loop/worktree_flow.py, ~L1525) builds its tuple from trackedness alone: for each of sprint_status and deferred_work, if git tracks it, it is protected. It never asks whether this task can actually carry the artifact.
For an ordinary completed story with empty harvested_deferrals, refiled_followups and story_closes_intended (and no pending harvest commit), all three deferred-work carry methods return without reaching commit_paths. The ledger is protected anyway, so an unstaged operator edit to it — outside the branch's incoming paths — makes clean_incoming_collisions pause the run even though neither the merge nor any post-merge carry could have committed that edit.
That is the unattended-halt class #460 and #618 exist to remove, reappearing through the protection added for the carry.
Why it is not obviously a false positive
The protection's own docstring justifies itself by what the carry can commit ("what makes the carry dangerous is committing a DIVERGENCE from a baseline somebody else authored"). Where there is no carry, that justification does not apply, so the refusal has no cause behind it — the operator is asked to move an edit that nothing was going to touch.
Not yet measured
I have not reproduced this. The reasoning above is a read of the code plus codex's report, and the reachability of "tracked ledger + no carry obligations + unstaged operator edit + isolated run" should be confirmed before any fix. In particular, check whether some other guard already refuses that shape first, which would make this inert.
Suggested shape
Derive the protected entry from the task's actual carry obligations rather than from trackedness alone — protect the sprint board unconditionally (it always advances) and the ledger only when this task has something to carry into it.
Per AGENTS.md's ablation rule, whatever guard changes here needs the "X is refused" test ablated to confirm it fails.
Found by codex during the #685 bot gate (review comment). Filed rather than fixed in #685: it belongs to the carry-obligation family rather than the merge-failure taxonomy that PR is about, and that PR is already carrying six rounds of fixes.
Claim
WorktreeFlow._protected_carry_paths(src/bmad_loop/worktree_flow.py, ~L1525) builds its tuple from trackedness alone: for each ofsprint_statusanddeferred_work, if git tracks it, it is protected. It never asks whether this task can actually carry the artifact.For an ordinary completed story with empty
harvested_deferrals,refiled_followupsandstory_closes_intended(and no pending harvest commit), all three deferred-work carry methods return without reachingcommit_paths. The ledger is protected anyway, so an unstaged operator edit to it — outside the branch's incoming paths — makesclean_incoming_collisionspause the run even though neither the merge nor any post-merge carry could have committed that edit.That is the unattended-halt class #460 and #618 exist to remove, reappearing through the protection added for the carry.
Why it is not obviously a false positive
The protection's own docstring justifies itself by what the carry can commit ("what makes the carry dangerous is committing a DIVERGENCE from a baseline somebody else authored"). Where there is no carry, that justification does not apply, so the refusal has no cause behind it — the operator is asked to move an edit that nothing was going to touch.
Not yet measured
I have not reproduced this. The reasoning above is a read of the code plus codex's report, and the reachability of "tracked ledger + no carry obligations + unstaged operator edit + isolated run" should be confirmed before any fix. In particular, check whether some other guard already refuses that shape first, which would make this inert.
Suggested shape
Derive the protected entry from the task's actual carry obligations rather than from trackedness alone — protect the sprint board unconditionally (it always advances) and the ledger only when this task has something to carry into it.
Per AGENTS.md's ablation rule, whatever guard changes here needs the "X is refused" test ablated to confirm it fails.