Skip to content

Commit dc0ca71

Browse files
phernandezclaude
andcommitted
fix: avoid Post(**metadata) crash when frontmatter contains 'content' or 'handler' keys
frontmatter.Post.__init__ takes `content` and `handler` as positional parameters. When user YAML frontmatter contains these as field names, unpacking metadata via **kwargs causes "got multiple values for argument 'content'". Replace frontmatter.loads() with frontmatter.parse() + Post() + update() in entity_parser, and replace the **metadata unpacking in entity_service.update_entity() with the same safe pattern. Fixes basic-memory-cloud#375 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: phernandez <paul@basicmachines.co>
1 parent 236ae26 commit dc0ca71

3 files changed

Lines changed: 45 additions & 4 deletions

File tree

src/basic_memory/markdown/entity_parser.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,9 +249,15 @@ async def parse_markdown_content(
249249

250250
content = strip_bom(content)
251251

252-
# Parse frontmatter with proper error handling for malformed YAML
252+
# Parse frontmatter with proper error handling for malformed YAML.
253+
# We use frontmatter.parse() instead of frontmatter.loads() because
254+
# loads() does Post(content, handler, **metadata), which crashes when
255+
# the YAML contains reserved keys like 'content' or 'handler'.
256+
# See basic-memory-cloud#375.
253257
try:
254-
post = frontmatter.loads(content)
258+
fm_metadata, fm_content = frontmatter.parse(content)
259+
post = frontmatter.Post(fm_content)
260+
post.metadata.update(fm_metadata)
255261
except yaml.YAMLError as e:
256262
logger.warning(
257263
f"Failed to parse YAML frontmatter in {file_path}: {e}. "

src/basic_memory/services/entity_service.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -361,8 +361,11 @@ async def update_entity(self, entity: EntityModel, schema: EntitySchema) -> Enti
361361
# in the existing file. Setting it unconditionally preserves the correct value.
362362
existing_markdown.frontmatter.metadata["permalink"] = new_permalink
363363

364-
# Create a new post with merged metadata
365-
merged_post = frontmatter.Post(post.content, **existing_markdown.frontmatter.metadata)
364+
# Create a new post with merged metadata.
365+
# Avoid **metadata unpacking — user frontmatter may contain reserved keys
366+
# like 'content' or 'handler' that conflict with Post.__init__ (cloud#375).
367+
merged_post = frontmatter.Post(post.content)
368+
merged_post.metadata.update(existing_markdown.frontmatter.metadata)
366369

367370
# write file
368371
final_content = dump_frontmatter(merged_post)

tests/markdown/test_date_frontmatter_parsing.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,3 +311,35 @@ async def test_parse_file_with_datetime_objects(tmp_path):
311311
assert "2025-10-24" in updated_at and "00:00:00" in updated_at, (
312312
f"Datetime at midnight should be normalized to ISO format, got: {updated_at}"
313313
)
314+
315+
316+
@pytest.mark.asyncio
317+
async def test_parse_file_with_reserved_frontmatter_field_content(tmp_path):
318+
"""Test that a 'content' field in frontmatter doesn't break parsing.
319+
320+
Reproduces basic-memory-cloud#375 where frontmatter containing a field named
321+
'content' causes frontmatter.Post.__init__() to receive multiple values for
322+
the 'content' positional argument.
323+
"""
324+
test_file = tmp_path / "topic-note-template.md"
325+
test_file.write_text(dedent("""\
326+
---
327+
title: Topic Note Template
328+
content: Template for topic notes
329+
handler: some-handler-value
330+
---
331+
332+
# Template Body
333+
334+
Actual body content here.
335+
"""))
336+
337+
parser = EntityParser(tmp_path)
338+
entity_markdown = await parser.parse_file(test_file)
339+
340+
assert entity_markdown.frontmatter.title == "Topic Note Template"
341+
# The 'content' and 'handler' fields should be preserved in metadata
342+
assert entity_markdown.frontmatter.metadata.get("content") == "Template for topic notes"
343+
assert entity_markdown.frontmatter.metadata.get("handler") == "some-handler-value"
344+
# The actual body content should be parsed correctly
345+
assert "Template Body" in entity_markdown.content

0 commit comments

Comments
 (0)