Skip to content

merge_local's pre-flight refusal message names only the merge-folding cause, but now also fires for the carry-sweep one #683

Description

@pbean

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:

  1. staged changes to tracked files outside the branch — the merge really can fold those in. The lead-in is accurate.
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Robustness, enhancement, tests, or docs worth schedulingarea:engineOrchestrator engine and run lifecyclebugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions