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.
Summary
update_frontmatterdoes 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:
Call
update_frontmatterwith{"status": "active"}. Result:Two changes nobody asked for.
createdandtagsswapped 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_byteexpectstitle: Home/tags:\n - alpha/nested:to come back asnested:/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 throughserde_yaml_ng. Two things follow.serde_json::Mapis not built withpreserve_orderhere, so it is a sorted map and the author's key order is lost on the way in. Andserde_yaml_ngemits 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 makesgit blameon 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.
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'spreserve_orderfeature) 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
rename_tag) hit this during triage. That operation deliberately avoids this machinery and edits tags in place, precisely so a 263-Note rename produces a one-word-per-Note diff. It does not fix this issue and does not depend on it.Environment
Reproduced against
developmentat 1c485ca, Linux,cargo test --lib.