Skip to content

Commit c1699df

Browse files
groksrcclaude
andcommitted
fix(core): preserve external_id during entity upsert on re-index
Force full re-index was generating new external_id UUIDs for every entity, breaking public share links that reference entities by their stable external_id. The upsert logic now preserves the existing external_id when resolving file_path conflicts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Drew Cain <groksrc@gmail.com>
1 parent e982900 commit c1699df

File tree

2 files changed

+47
-0
lines changed

2 files changed

+47
-0
lines changed

src/basic_memory/repository/entity_repository.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,9 @@ async def upsert_entity(self, entity: Entity) -> Entity:
388388
# Use merge to avoid session state conflicts
389389
# Set the ID to update existing entity
390390
entity.id = existing_entity.id
391+
# Preserve the stable external_id so that external references
392+
# (e.g. public share links) survive re-indexing
393+
entity.external_id = existing_entity.external_id
391394

392395
# Ensure observations reference the correct entity_id
393396
for obs in entity.observations:

tests/repository/test_entity_repository_upsert.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,50 @@ async def test_upsert_entity_same_file_update(entity_repository: EntityRepositor
7070
assert result2.file_path == "test/test-entity.md"
7171

7272

73+
@pytest.mark.asyncio
74+
async def test_upsert_entity_preserves_external_id(entity_repository: EntityRepository):
75+
"""Test that upserting an entity with the same file_path preserves the original external_id.
76+
77+
Trigger: force full re-index creates a new Entity model (with a fresh UUID)
78+
for a file that already has a database record
79+
Why: external_id is used by public share links — if it changes, shares break
80+
Outcome: the original external_id survives the upsert
81+
"""
82+
# Create initial entity
83+
entity1 = Entity(
84+
project_id=entity_repository.project_id,
85+
title="Shared Note",
86+
note_type="note",
87+
permalink="test/shared-note",
88+
file_path="test/shared-note.md",
89+
content_type="text/markdown",
90+
external_id="original-stable-id",
91+
created_at=datetime.now(timezone.utc),
92+
updated_at=datetime.now(timezone.utc),
93+
)
94+
result1 = await entity_repository.upsert_entity(entity1)
95+
assert result1.external_id == "original-stable-id"
96+
97+
# Simulate re-index: new Entity model with a DIFFERENT external_id
98+
entity2 = Entity(
99+
project_id=entity_repository.project_id,
100+
title="Shared Note (updated)",
101+
note_type="note",
102+
permalink="test/shared-note",
103+
file_path="test/shared-note.md",
104+
content_type="text/markdown",
105+
external_id="newly-generated-uuid", # would break share links
106+
created_at=datetime.now(timezone.utc),
107+
updated_at=datetime.now(timezone.utc),
108+
)
109+
result2 = await entity_repository.upsert_entity(entity2)
110+
111+
# ID preserved, title updated, external_id stable
112+
assert result2.id == result1.id
113+
assert result2.title == "Shared Note (updated)"
114+
assert result2.external_id == "original-stable-id"
115+
116+
73117
@pytest.mark.asyncio
74118
async def test_upsert_entity_permalink_conflict_different_file(entity_repository: EntityRepository):
75119
"""Test upserting an entity with permalink conflict but different file_path."""

0 commit comments

Comments
 (0)