Skip to content

Commit f506507

Browse files
fix: ensure permalinks are generated for entities with null permalinks during move operations (#162)
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
1 parent 8a065c3 commit f506507

2 files changed

Lines changed: 72 additions & 3 deletions

File tree

src/basic_memory/services/entity_service.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -682,8 +682,8 @@ async def move_entity(
682682
# 6. Prepare database updates
683683
updates = {"file_path": destination_path}
684684

685-
# 7. Update permalink if configured
686-
if app_config.update_permalinks_on_move:
685+
# 7. Update permalink if configured or if entity has null permalink
686+
if app_config.update_permalinks_on_move or old_permalink is None:
687687
# Generate new permalink from destination path
688688
new_permalink = await self.resolve_permalink(destination_path)
689689

@@ -693,7 +693,10 @@ async def move_entity(
693693
)
694694

695695
updates["permalink"] = new_permalink
696-
logger.info(f"Updated permalink: {old_permalink} -> {new_permalink}")
696+
if old_permalink is None:
697+
logger.info(f"Generated permalink for entity with null permalink: {new_permalink}")
698+
else:
699+
logger.info(f"Updated permalink: {old_permalink} -> {new_permalink}")
697700

698701
# 8. Recalculate checksum
699702
new_checksum = await self.file_service.compute_checksum(destination_path)

tests/services/test_entity_service.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1728,3 +1728,69 @@ async def test_move_entity_with_complex_observations(
17281728
assert "Branch Strategy" in relation_targets
17291729
assert "Multiple" in relation_targets
17301730
assert "Links" in relation_targets
1731+
1732+
1733+
@pytest.mark.asyncio
1734+
async def test_move_entity_with_null_permalink_generates_permalink(
1735+
entity_service: EntityService,
1736+
project_config: ProjectConfig,
1737+
entity_repository: EntityRepository,
1738+
):
1739+
"""Test that moving entity with null permalink generates a new permalink automatically.
1740+
1741+
This tests the fix for issue #155 where entities with null permalinks from the database
1742+
migration would fail validation when being moved. The fix ensures that entities with
1743+
null permalinks get a generated permalink during move operations, regardless of the
1744+
update_permalinks_on_move setting.
1745+
"""
1746+
# Create entity through direct database insertion to simulate migrated entity with null permalink
1747+
from basic_memory.models.knowledge import Entity as EntityModel
1748+
from datetime import datetime, timezone
1749+
1750+
# Create an entity with null permalink directly in database (simulating migrated data)
1751+
entity_data = {
1752+
"title": "Test Entity",
1753+
"file_path": "test/null-permalink-entity.md",
1754+
"entity_type": "note",
1755+
"content_type": "text/markdown",
1756+
"permalink": None, # This is the key - null permalink from migration
1757+
"created_at": datetime.now(timezone.utc),
1758+
"updated_at": datetime.now(timezone.utc),
1759+
}
1760+
1761+
# Create the entity directly in database
1762+
created_entity = await entity_repository.create(entity_data)
1763+
assert created_entity.permalink is None
1764+
1765+
# Create the physical file
1766+
file_path = project_config.home / created_entity.file_path
1767+
file_path.parent.mkdir(parents=True, exist_ok=True)
1768+
file_path.write_text("# Test Entity\n\nContent here.")
1769+
1770+
# Configure move without permalink updates (the default setting that previously triggered the bug)
1771+
app_config = BasicMemoryConfig(update_permalinks_on_move=False)
1772+
1773+
# Move entity - this should now succeed and generate a permalink
1774+
moved_entity = await entity_service.move_entity(
1775+
identifier=created_entity.title, # Use title since permalink is None
1776+
destination_path="moved/test-entity.md",
1777+
project_config=project_config,
1778+
app_config=app_config,
1779+
)
1780+
1781+
# Verify the move succeeded and a permalink was generated
1782+
assert moved_entity is not None
1783+
assert moved_entity.file_path == "moved/test-entity.md"
1784+
assert moved_entity.permalink is not None
1785+
assert moved_entity.permalink != ""
1786+
1787+
# Verify the moved entity can be used to create an EntityResponse without validation errors
1788+
from basic_memory.schemas.response import EntityResponse
1789+
response = EntityResponse.model_validate(moved_entity)
1790+
assert response.permalink == moved_entity.permalink
1791+
1792+
# Verify the physical file was moved
1793+
old_path = project_config.home / "test/null-permalink-entity.md"
1794+
new_path = project_config.home / "moved/test-entity.md"
1795+
assert not old_path.exists()
1796+
assert new_path.exists()

0 commit comments

Comments
 (0)