Skip to content

Maximum merging can wipe out entire list properties: two merge groups write the same key and the second overwrites the first #176

Description

@matt-edmondson

What's wrong

MergeSimilarProperties (Frontmatter/PropertyMerger.cs:40-52) writes each canonical-name group into mergedFrontmatter without checking whether another group has already written that key. This goes wrong under Maximum:

  1. The name mapping under Maximum is not transitive (see Maximum merging depends on key order: name_of_author + author_name merge in one order and stay separate in the other, because each key is scored against itself #167).
  2. So one group can end up with group.Key = X, while a lone key's group falls back to its own name, also X, through the canonicalKey rule (:45-49).
  3. MergeArrayValues then does target[canonicalKey] = … (:181) for both groups.
  4. The later group replaces the earlier group's merged list.

Reproduction (HEAD 43a9cdc)

Frontmatter.CombineFrontmatter(
  "---\nauthor_name: [a1]\nauthor: [b1]\nname_of_author: [c1]\n---\nBody\n",
  FrontmatterNaming.AsIs, FrontmatterOrder.AsIs, FrontmatterMergeStrategy.Maximum);

Observed:

author:
- b1

a1 and c1 are gone. It is the same with two items per list: [a1,a2], [b1,b2] and [c1,c2] give just author: [b1, b2].

Trace, from calling GetCanonicalName via reflection:

A random fuzz over 2–6 keys, all with list values, found this loss only under Maximum. Two of the inputs it hit:

  • [keywords, author_name, author, name_of_author]
  • [desc, writer, name_of_author, authors, description, author], which loses writer, name_of_author and authors

Why it matters

List merging is meant to be lossless: items are appended, and repeats are kept per #142. Here whole lists disappear with no warning.

#167 reports the order-dependent mapping, which is the root cause. Fixing its scoring alone would still leave MergeSimilarProperties with no guard against two groups writing the same key. This issue asks for that guard.

Suggested fix / acceptance criteria

  • Make the writes in MergeSimilarProperties collision-safe. When the target key is already present, either merge into it (append list items, apply the scalar rule) or keep the colliding keys apart.
  • Add a test invariant: for every strategy, the multiset of list items in the input equals the multiset in the output.
  • Acceptance:
    • The repro keeps a1, b1 and c1.
    • A permutation or fuzz test over key orders under Maximum loses no list item.

Related: #167 (root cause) and PR #173. The PR changes the lone-list canonicalKey rule but doesn't fix this, because here canonicalKey already equals the original key.

Activity

  1. matt-edmondson commented on Sep 30, 2026

    @matt-edmondson
    ContributorAuthor

    Triage


    Generated by Claude Code

  2. matt-edmondson commented on Sep 30, 2026

    @matt-edmondson
    ContributorAuthor

    Covered by #180, which redesigns this bug cluster as a whole. Implement it through that issue rather than individually.


    Generated by Claude Code

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions