Skip to content

update_frontmatter regenerates the whole frontmatter block: keys alphabetised and one-line lists exploded #257

Description

@BattermanZ

This was generated by AI during triage.

Summary

update_frontmatter does not edit a Note's frontmatter, it regenerates it. Changing one unrelated key rewrites the whole block: every key is re-sorted alphabetically, and one-line YAML lists are exploded into block lists. The Note's body is preserved byte for byte, but everything between the --- markers is discarded and re-emitted.

Callers reach for this tool to set one field. They get a reformatted frontmatter block on every Note they touch.

Reproduction

Add one key to a Note with a typical Obsidian-style frontmatter block:

---
tags: [type/project, status/active, domain/plans, topic/kids]
created: 2026-03-18
---

Call update_frontmatter with {"status": "active"}. Result:

---
created: 2026-03-18
status: active
tags:
- type/project
- status/active
- domain/plans
- topic/kids
---

Two changes nobody asked for. created and tags swapped places because the keys are now in alphabetical order, and the one-line tag list became a five-line block list.

This is not a regression report from the field. The behavior is asserted by the repository's own test suite: update_note_frontmatter_merges_top_level_keys_and_keeps_the_body_byte_for_byte expects title: Home / tags:\n - alpha / nested: to come back as nested: / status: / tags:\n- alpha / title: Home. The test comment records the cause without treating it as a defect: "serde_json maps serialize deterministically (keys sorted)".

Cause

The merge parses the frontmatter block into a serde_json::Map, applies the updates, and serializes the whole map back through serde_yaml_ng. Two things follow.

serde_json::Map is not built with preserve_order here, so it is a sorted map and the author's key order is lost on the way in. And serde_yaml_ng emits sequences in block form, so any flow-style list is converted regardless of how it was written.

The write itself is careful. It rewrites exactly the span between the markers and leaves the body untouched. The loss happens in the round trip through the map, not in the file handling.

Why it matters

The damage scales with how much you use the tool. On a Vault of 553 Notes where 380 carry a one-line tags: list, any agent doing routine frontmatter maintenance reformats each Note it touches. The git diff for a one-field change is a rewritten frontmatter block, which makes the actual change hard to see and makes git blame on frontmatter close to useless.

It also means the tool cannot be used for bulk work. Anything that touches frontmatter across many Notes produces a diff whose size is unrelated to the size of the change.

Suggested direction

Preserve what the author wrote, and touch only the keys the caller named.

  • Keep the existing key order. Keys the caller did not mention should come back in their original positions; genuinely new keys appended at the end is the obvious rule.
  • Keep each value's existing YAML style. A one-line list stays a one-line list, a block list stays a block list.
  • Leave indentation, quoting, and comments alone where a key was not touched.

Whether that is achievable by swapping in an order-preserving map and a style-aware emitter, or needs surgical span edits per key, is an implementation question worth deciding before the work starts. The order-preserving map fixes half of it cheaply (serde_json's preserve_order feature) and is worth checking first, but it does not address flow versus block style.

Worth grilling before it goes to an agent. Deciding whether comments inside frontmatter must survive, and what happens to a block the tool cannot parse, will shape the approach.

Related

Environment

Reproduced against development at 1c485ca, Linux, cargo test --lib.

Activity

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingready-for-agentImplementation-ready for an engineering agent

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions