Skip to content

Commit e3ced49

Browse files
phernandezclaude
andauthored
fix: prevent spurious 'metadata: {}' in frontmatter output (#530)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 8f962fd commit e3ced49

2 files changed

Lines changed: 95 additions & 1 deletion

File tree

‎src/basic_memory/markdown/entity_parser.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,9 @@ async def parse_markdown_content(
241241
f"Failed to parse YAML frontmatter in {file_path}: {e}. "
242242
f"Treating file as plain markdown without frontmatter."
243243
)
244-
post = frontmatter.Post(content, metadata={})
244+
# Use Post(content) not Post(content, metadata={})
245+
# The latter creates {"metadata": {}} in the metadata dict (issue #528)
246+
post = frontmatter.Post(content)
245247

246248
# Normalize frontmatter values
247249
metadata = normalize_frontmatter_metadata(post.metadata)

‎tests/markdown/test_entity_parser_error_handling.py‎

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,3 +291,95 @@ async def test_parse_valid_file_still_works(tmp_path):
291291
assert result.frontmatter.title == "Valid File"
292292
assert result.frontmatter.type == "knowledge"
293293
assert result.frontmatter.tags == ["test", "valid"]
294+
295+
296+
@pytest.mark.asyncio
297+
async def test_invalid_yaml_does_not_add_metadata_key(tmp_path):
298+
"""Test that invalid YAML doesn't create spurious 'metadata' key in frontmatter (issue #528).
299+
300+
This tests a bug where `frontmatter.Post(content, metadata={})` was used incorrectly.
301+
The `metadata={}` kwarg creates a KEY called "metadata" in the metadata dict,
302+
rather than setting the metadata to an empty dict.
303+
304+
This caused files with invalid YAML to get `metadata: {}` in their frontmatter output,
305+
which is incorrect and confusing.
306+
"""
307+
# Create a file with completely broken YAML that will trigger the fallback path
308+
test_file = tmp_path / "broken_yaml.md"
309+
content = dedent(
310+
"""
311+
---
312+
title: Invalid YAML
313+
this is: [not, valid, yaml
314+
missing: closing bracket
315+
---
316+
# Content
317+
318+
This file has broken YAML frontmatter.
319+
"""
320+
).strip()
321+
test_file.write_text(content)
322+
323+
# Parse the file
324+
parser = EntityParser(tmp_path)
325+
result = await parser.parse_file(test_file)
326+
327+
# The metadata dict should NOT contain a "metadata" key
328+
# This was the bug: frontmatter.Post(content, metadata={}) creates {"metadata": {}}
329+
assert "metadata" not in result.frontmatter.metadata, (
330+
"Frontmatter metadata should not contain a 'metadata' key. "
331+
"This indicates the bug where Post(content, metadata={}) was used incorrectly."
332+
)
333+
334+
# Should still have the expected defaults
335+
assert result.frontmatter.title == "broken_yaml"
336+
assert result.frontmatter.type == "note"
337+
338+
339+
@pytest.mark.asyncio
340+
async def test_frontmatter_roundtrip_preserves_user_metadata(tmp_path):
341+
"""Test that parsing and re-serializing frontmatter preserves user fields (issue #528).
342+
343+
Users reported that after cloud sync, their custom frontmatter fields like 'citekey'
344+
were being lost and replaced with defaults. This test ensures user metadata is preserved.
345+
"""
346+
from basic_memory.file_utils import dump_frontmatter
347+
import frontmatter
348+
349+
# Create a file with user's custom frontmatter (like the bug report)
350+
test_file = tmp_path / "litnote.md"
351+
content = dedent(
352+
'''
353+
---
354+
title: "My Document Title"
355+
type: litnote
356+
tags:
357+
- research
358+
- methodology
359+
citekey: authorTitleYear2024
360+
---
361+
362+
# Content here...
363+
'''
364+
).strip()
365+
test_file.write_text(content)
366+
367+
# Parse the file
368+
parser = EntityParser(tmp_path)
369+
result = await parser.parse_file(test_file)
370+
371+
# User's custom fields should be preserved
372+
assert result.frontmatter.title == "My Document Title"
373+
assert result.frontmatter.type == "litnote" # NOT overwritten to "note"
374+
assert "citekey" in result.frontmatter.metadata
375+
assert result.frontmatter.metadata["citekey"] == "authorTitleYear2024"
376+
377+
# Simulate what write_frontmatter does
378+
post = frontmatter.Post(result.content, **result.frontmatter.metadata)
379+
output = dump_frontmatter(post)
380+
381+
# The output should NOT have duplicate frontmatter or metadata: {} key
382+
assert output.count("---") == 2, "Should have exactly one frontmatter block (two --- delimiters)"
383+
assert "metadata:" not in output, "Should not have 'metadata:' key in output"
384+
assert "citekey: authorTitleYear2024" in output, "User's citekey should be preserved"
385+
assert "type: litnote" in output, "User's type should be preserved"

0 commit comments

Comments
 (0)