Skip to content

Protect the deferred-work ledger only when the task can actually carry it #687

Description

@pbean

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions