Bug Description
Five of 149 notes in a local synced project were found with two frontmatter blocks — a stub containing only permalink, then a blank line, then the note's real frontmatter:
---
permalink: myproject/my-note
---
---
name: my-note
description: The description that actually explains what this note is
metadata:
node_type: memory
type: reference
permalink: myproject/my-note
---
# real body starts here
Any consumer that parses the first frontmatter document — the normal behavior for YAML frontmatter — sees only permalink. name and description are invisible.
In my setup description is what an agent reads to decide whether a note is relevant, so those five notes became effectively unfindable despite having perfectly good descriptions. One of them was a 40KB note I rely on constantly. The failure is silent: nothing errors, the file looks fine when you open it, and the description is right there — just in the second block.
Steps To Reproduce
I could not reproduce this on demand. That's the main weakness of this report and I'd rather say so than invent a repro.
Two controlled probes on 0.22.1 both behaved correctly:
- Note with
permalink inside a single block → untouched by sync (md5 stable across 12s of polling).
- Note without
permalink, single block → sync merged permalink into the existing block, correctly. No second block.
What the five affected notes had in common: all carried older-style frontmatter — name / description / type / originSessionId at top level, with no metadata: wrapper and no permalink — i.e. the shape an earlier version produced. Two of the five were files I had just rewritten by hand in roughly that older shape, and the stub reappeared immediately after each write. Once I rewrote them as a single canonical block with permalink inside, it stopped recurring.
Best guess: a path where a note whose on-disk frontmatter doesn't line up with the DB record gets a permalink block emitted rather than merged. I couldn't isolate it further.
Expected Behavior
ensure_frontmatter_on_sync should always merge into the note's existing frontmatter block, and never emit a second YAML document ahead of it.
Actual Behavior
A second block is prepended before the real one, shadowing name and description for any first-block parser.
Environment
- OS: macOS 26.5.2 (Darwin 25.5.0), arm64
- Python version: 3.9 (system python3 used for the audit); basic-memory installed under its own uv-managed interpreter
- Basic Memory version: 0.22.1
- Installation method: uv tool (
~/.local/share/uv/tools/basic-memory), running as the Claude Code plugin basicmachines-co/basic-memory 0.22.1
- Claude Desktop version: 1.24012.9 (Claude Code 2.1.219)
Additional Context
Relevant config from ~/.basic-memory/config.json:
{
"env": "dev",
"ensure_frontmatter_on_sync": true,
"sync_changes": true,
"sync_delay": 1000,
"permalinks_include_project": true,
"kebab_filenames": false,
"disable_permalinks": false
}
Two suggestions that hold regardless of whether the exact trigger is found:
- Make the writer merge-only. Locate the first
--- … --- document and update it in place; never emit a new one ahead of it. That makes the bad state unreachable even if the triggering path stays unknown.
- Add a detector. A health check — or a warning during sync — for any note with more than one leading frontmatter block would have surfaced this immediately. As it was, it sat undetected for weeks; I only found it because one note's description came back empty during an audit and I went looking for why.
Happy to run diagnostics against a specific code path if that would help narrow it down.
Bug Description
Five of 149 notes in a local synced project were found with two frontmatter blocks — a stub containing only
permalink, then a blank line, then the note's real frontmatter:Any consumer that parses the first frontmatter document — the normal behavior for YAML frontmatter — sees only
permalink.nameanddescriptionare invisible.In my setup
descriptionis what an agent reads to decide whether a note is relevant, so those five notes became effectively unfindable despite having perfectly good descriptions. One of them was a 40KB note I rely on constantly. The failure is silent: nothing errors, the file looks fine when you open it, and the description is right there — just in the second block.Steps To Reproduce
I could not reproduce this on demand. That's the main weakness of this report and I'd rather say so than invent a repro.
Two controlled probes on 0.22.1 both behaved correctly:
permalinkinside a single block → untouched by sync (md5 stable across 12s of polling).permalink, single block → sync mergedpermalinkinto the existing block, correctly. No second block.What the five affected notes had in common: all carried older-style frontmatter —
name/description/type/originSessionIdat top level, with nometadata:wrapper and nopermalink— i.e. the shape an earlier version produced. Two of the five were files I had just rewritten by hand in roughly that older shape, and the stub reappeared immediately after each write. Once I rewrote them as a single canonical block withpermalinkinside, it stopped recurring.Best guess: a path where a note whose on-disk frontmatter doesn't line up with the DB record gets a permalink block emitted rather than merged. I couldn't isolate it further.
Expected Behavior
ensure_frontmatter_on_syncshould always merge into the note's existing frontmatter block, and never emit a second YAML document ahead of it.Actual Behavior
A second block is prepended before the real one, shadowing
nameanddescriptionfor any first-block parser.Environment
~/.local/share/uv/tools/basic-memory), running as the Claude Code pluginbasicmachines-co/basic-memory0.22.1Additional Context
Relevant config from
~/.basic-memory/config.json:{ "env": "dev", "ensure_frontmatter_on_sync": true, "sync_changes": true, "sync_delay": 1000, "permalinks_include_project": true, "kebab_filenames": false, "disable_permalinks": false }Two suggestions that hold regardless of whether the exact trigger is found:
---…---document and update it in place; never emit a new one ahead of it. That makes the bad state unreachable even if the triggering path stays unknown.Happy to run diagnostics against a specific code path if that would help narrow it down.