Skip to content

Commit a3e4bad

Browse files
committed
fix(core): preserve empty frontmatter permalink semantics
Signed-off-by: phernandez <paul@basicmachines.co>
1 parent 58dd696 commit a3e4bad

File tree

2 files changed

+55
-20
lines changed

2 files changed

+55
-20
lines changed

src/basic_memory/services/entity_service.py

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ class EntityWriteResult:
6060
search_content: str
6161

6262

63+
def _frontmatter_permalink(value: object) -> str | None:
64+
"""Return an explicit frontmatter permalink only when YAML parsed a real string."""
65+
return value if isinstance(value, str) and value else None
66+
67+
6368
class EntityService(BaseService[EntityModel]):
6469
"""Service for managing entities in the database."""
6570

@@ -287,11 +292,13 @@ async def create_entity_with_content(self, schema: EntitySchema) -> EntityWriteR
287292
schema.note_type = content_frontmatter["type"]
288293

289294
if "permalink" in content_frontmatter:
290-
content_markdown = self._build_frontmatter_markdown(
291-
schema.title,
292-
schema.note_type,
293-
_coerce_to_string(content_frontmatter["permalink"]),
294-
)
295+
content_permalink = _frontmatter_permalink(content_frontmatter["permalink"])
296+
if content_permalink is not None:
297+
content_markdown = self._build_frontmatter_markdown(
298+
schema.title,
299+
schema.note_type,
300+
content_permalink,
301+
)
295302

296303
# Get unique permalink (prioritizing content frontmatter) unless disabled
297304
if self.app_config and self.app_config.disable_permalinks:
@@ -394,11 +401,13 @@ async def update_entity_with_content(
394401
schema.note_type = content_frontmatter["type"]
395402

396403
if "permalink" in content_frontmatter:
397-
content_markdown = self._build_frontmatter_markdown(
398-
schema.title,
399-
schema.note_type,
400-
_coerce_to_string(content_frontmatter["permalink"]),
401-
)
404+
content_permalink = _frontmatter_permalink(content_frontmatter["permalink"])
405+
if content_permalink is not None:
406+
content_markdown = self._build_frontmatter_markdown(
407+
schema.title,
408+
schema.note_type,
409+
content_permalink,
410+
)
402411

403412
# Check if we need to update the permalink based on content frontmatter (unless disabled)
404413
new_permalink = entity.permalink # Default to existing
@@ -525,11 +534,13 @@ async def fast_write_entity(
525534
schema.note_type = content_frontmatter["type"]
526535

527536
if "permalink" in content_frontmatter:
528-
content_markdown = self._build_frontmatter_markdown(
529-
schema.title,
530-
schema.note_type,
531-
_coerce_to_string(content_frontmatter["permalink"]),
532-
)
537+
content_permalink = _frontmatter_permalink(content_frontmatter["permalink"])
538+
if content_permalink is not None:
539+
content_markdown = self._build_frontmatter_markdown(
540+
schema.title,
541+
schema.note_type,
542+
content_permalink,
543+
)
533544

534545
# --- Permalink Resolution ---
535546
if self.app_config and self.app_config.disable_permalinks:
@@ -668,11 +679,13 @@ async def fast_edit_entity(
668679
update_data["note_type"] = _coerce_to_string(content_frontmatter["type"])
669680

670681
if "permalink" in content_frontmatter:
671-
content_markdown = self._build_frontmatter_markdown(
672-
_coerce_to_string(update_data.get("title", entity.title)),
673-
_coerce_to_string(update_data.get("note_type", entity.note_type)),
674-
_coerce_to_string(content_frontmatter["permalink"]),
675-
)
682+
content_permalink = _frontmatter_permalink(content_frontmatter["permalink"])
683+
if content_permalink is not None:
684+
content_markdown = self._build_frontmatter_markdown(
685+
_coerce_to_string(update_data.get("title", entity.title)),
686+
_coerce_to_string(update_data.get("note_type", entity.note_type)),
687+
content_permalink,
688+
)
676689

677690
metadata = normalize_frontmatter_metadata(content_frontmatter or {})
678691
update_data["entity_metadata"] = {k: v for k, v in metadata.items() if v is not None}

tests/services/test_entity_service_write_result.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,28 @@ async def test_create_entity_with_content_returns_full_and_search_content(
2727
assert result.search_content == "Create body content"
2828

2929

30+
@pytest.mark.asyncio
31+
@pytest.mark.parametrize("permalink_line", ["permalink:", "permalink: null", 'permalink: ""'])
32+
async def test_create_entity_ignores_empty_frontmatter_permalink(
33+
entity_service, file_service, permalink_line: str
34+
) -> None:
35+
result = await entity_service.create_entity_with_content(
36+
EntitySchema(
37+
title="Empty Frontmatter Permalink",
38+
directory="notes",
39+
note_type="note",
40+
content=f"---\n{permalink_line}\n---\nCreate body content",
41+
)
42+
)
43+
44+
file_path = file_service.get_entity_path(result.entity)
45+
file_content, _ = await file_service.read_file(file_path)
46+
47+
assert result.entity.permalink == "test-project/notes/empty-frontmatter-permalink"
48+
assert "permalink: test-project/notes/empty-frontmatter-permalink" in file_content
49+
assert "permalink: None" not in file_content
50+
51+
3052
@pytest.mark.asyncio
3153
async def test_update_entity_with_content_returns_full_and_search_content(
3254
entity_service, file_service

0 commit comments

Comments
 (0)