What happens
When a note's on-disk filename doesn't match the canonical {id}-{topic}.md derived from its frontmatter, fieldnotes verify --update writes the healed copy to the canonical name and leaves the original file behind — silently forking the note into two files with the same id.
Real-world repro (found 2026-06-11 healing a production repo)
A note lived at 0008-ssr-jwt-broken-for-writes-in-route-handlers.md with frontmatter id: '0008', topic: ssr-jwt-broken-for-writes. Running verify --update:
update_shas healed the note object
write_note computed the target from note.filename() → 0008-ssr-jwt-broken-for-writes.md
- Result: a new healed file AND the untouched original —
get 8 then fails with "multiple notes match"
Minimal repro
fieldnotes init
echo x > f.py
fieldnotes add --topic t --title T --body b --refs f.py
mv .fieldnotes/notes/0001-t.md .fieldnotes/notes/0001-t-renamed.md
echo y > f.py
fieldnotes verify --update
ls .fieldnotes/notes/ # two 0001 files
Fix sketch
verify already knows the source path (NoteStatus.path). The heal loop in cli.py should write back to that path instead of recomputing from filename() — or detect the mismatch and rename, with a line of output. Either is fine; silent forking is not (the house rule: nothing fails silently).
Tests: one for heal-in-place on a mismatched filename, one asserting no duplicate gets created.
What happens
When a note's on-disk filename doesn't match the canonical
{id}-{topic}.mdderived from its frontmatter,fieldnotes verify --updatewrites the healed copy to the canonical name and leaves the original file behind — silently forking the note into two files with the same id.Real-world repro (found 2026-06-11 healing a production repo)
A note lived at
0008-ssr-jwt-broken-for-writes-in-route-handlers.mdwith frontmatterid: '0008',topic: ssr-jwt-broken-for-writes. Runningverify --update:update_shashealed the note objectwrite_notecomputed the target fromnote.filename()→0008-ssr-jwt-broken-for-writes.mdget 8then fails with "multiple notes match"Minimal repro
Fix sketch
verifyalready knows the source path (NoteStatus.path). The heal loop incli.pyshould write back to that path instead of recomputing fromfilename()— or detect the mismatch and rename, with a line of output. Either is fine; silent forking is not (the house rule: nothing fails silently).Tests: one for heal-in-place on a mismatched filename, one asserting no duplicate gets created.