Skip to content

Field-level merge resolves scalar conflicts by lexicographic order, so the older write wins and --prefer changes nothing in either direction #1184

Closed

Description

@unbraind

Reproduced on pm-cli 2026.9.3 (global install, Node 26.5.0) in a throwaway repository with pm merge install freshly run.

What happens

When two branches set the same scalar field to different values, the field-level driver resolves the conflict by stable_value_order. In practice that selects the lexicographically smaller value, and it does so regardless of which side is ours, regardless of --prefer, and regardless of which write is newer.

Reproduction

One item, two branches, each setting status. The branch that writes blocked commits first; the branch that writes in_progress commits three seconds later. Then merge the earlier branch into the later one:

earlier(blocked)     commit 2026-09-03T19:30:07+02:00   updated_at "2026-09-03T17:30:07.414Z"
later(in_progress)   commit 2026-09-03T19:30:10+02:00   updated_at "2026-09-03T17:30:10.251Z"

$ git checkout late-inprogress
$ git merge early-blocked
CONFLICT (content): Merge conflict in .agents/pm/tasks/pm-i0ea.toon

$ grep ^status: .agents/pm/tasks/pm-i0ea.toon
status: blocked

The older value wins. Both sides carry updated_at, so the information needed to prefer the newer write is present in the inputs and is not consulted.

The same ordering decides numeric fields: with priority: 3 on one side and priority: 1 on the other, 1 wins.

--prefer has no effect

pm merge --help documents --prefer <side> as "side that wins unresolvable conflicts (ours|theirs; default ours)". It does not win these:

driver invocation ours theirs result
default (--prefer ours implied) in_progress / 3 blocked / 1 blocked / 1
explicit --prefer theirs in_progress / 3 blocked / 1 blocked / 1
default, merged in the reverse direction blocked / 1 in_progress / 3 blocked / 1

Three different configurations, one outcome. The receipt honestly records what was asked — requested_preference: "ours" or "theirs" as appropriate, which is a real improvement over the constant reported in #974 — but the requested side never decides anything, so the option reads as a control that is not wired to the behaviour it documents.

Why this matters for the multi-agent case

Direction-independent convergence is genuinely the right property for concurrent agents: two agents merging each other must not end up with different trees. The problem is not determinism, it is the ordering key. Sorting values alphabetically makes the winner an artefact of spelling:

  • blocked beats in_progress because b < i
  • closed beats in_progress because c < i, so a stale agent that closed an item silently reverts a peer that had just started work on it
  • priority: 1 beats priority: 3 regardless of who set it or when

An agent has no way to reason about this. It cannot predict the winner from its own intent, only from the alphabet.

Suggested direction

Order by updated_at (newest wins), falling back to the current stable order only when the timestamps are identical. That keeps determinism and direction-independence — both sides already agree on both timestamps, so both merge directions still converge — while making the winner the write that actually happened last. If lexicographic order is deliberate, then --prefer should either be honoured for these conflicts or documented as not applying to them, because as written it promises a control that does nothing.

Environment

  • pm-cli 2026.9.3, Node 26.5.0, Linux
  • Fresh pm init + pm merge install, drivers confirmed registered in .git/config
  • Item hash version 3

Activity

  1. unbraind commented on Sep 3, 2026

    @unbraind
    OwnerAuthor

    Canonical PM lineage: pm-7wzb6d — scalar merge conflict ordering ignores recency and requested preference. It is linked as a recurrence/semantic-policy extension of the shipped direction-independence and truthful-receipt work, rather than reopening those completed baselines.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions