Found while wiring protected for #618 (phase 4). Message-only; out of scope there.
What is wrong
WorktreeFlow.merge_local's escalation text for a GitError out of clean_incoming_collisions reads:
merge of <branch> into <target> blocked: the target checkout has uncommitted changes to tracked files outside this branch — a merge or squash would fold them into this story's commit. Commit, stash or revert them, then `bmad-loop resume <run>`. One cause is a per_worktree engine Editor writing into the main checkout; another is ordinary local work. <inner GitError>
Since #618 that raise has two causes, and the lead-in describes only the first:
- staged changes to tracked files outside the branch — the merge really can fold those in. The lead-in is accurate.
- dirt on a path the run itself commits after the merge (
protected). Here the merge folds nothing; commit_paths does, in the run's own bookkeeping commit. "a merge or squash would fold them into this story's commit" names the wrong mechanism.
The inner GitError appended as {e} is accurate for both — phase 3 gave it two separately-worded clauses, one per remedy — so the operator does get the right diagnosis, just after a lead-in that contradicts it.
"tracked files" is also not quite right for cause 2 in principle: protected blocks whatever the index column says. It happens to be accurate today only because _carried_artifact_rels restricts itself to tracked paths (see the sibling issue on untracked artifacts).
Why it was not fixed in place
tests/test_engine_worktree.py::test_merge_stray_dirt_escalates_with_clear_message pins six properties of this string, and phase 3 had just settled that taxonomy. Rewording is a small change that should land with its assertions re-cut deliberately rather than as a side effect of the wiring commit.
Suggested shape
Keep the two SAFE resolutions and the negative properties #460 won (no Unity attribution, no "clean them"), and make the lead-in cover both causes without asserting either mechanism — leaving the inner clauses to say which one fired.
Found while wiring
protectedfor #618 (phase 4). Message-only; out of scope there.What is wrong
WorktreeFlow.merge_local's escalation text for aGitErrorout ofclean_incoming_collisionsreads:Since #618 that raise has two causes, and the lead-in describes only the first:
protected). Here the merge folds nothing;commit_pathsdoes, in the run's own bookkeeping commit. "a merge or squash would fold them into this story's commit" names the wrong mechanism.The inner
GitErrorappended as{e}is accurate for both — phase 3 gave it two separately-worded clauses, one per remedy — so the operator does get the right diagnosis, just after a lead-in that contradicts it."tracked files" is also not quite right for cause 2 in principle:
protectedblocks whatever the index column says. It happens to be accurate today only because_carried_artifact_relsrestricts itself to tracked paths (see the sibling issue on untracked artifacts).Why it was not fixed in place
tests/test_engine_worktree.py::test_merge_stray_dirt_escalates_with_clear_messagepins six properties of this string, and phase 3 had just settled that taxonomy. Rewording is a small change that should land with its assertions re-cut deliberately rather than as a side effect of the wiring commit.Suggested shape
Keep the two SAFE resolutions and the negative properties #460 won (no Unity attribution, no "clean them"), and make the lead-in cover both causes without asserting either mechanism — leaving the inner clauses to say which one fired.