Rescoped 2026-08-04 after review. permalink IS NULL for non-markdown files is the intended design, not a defect — see discussion. What remains is dead code plus an undocumented invariant. The original, wider framing is preserved in this issue's history.
What remains
1. A dead resolve_permalink() call. batch_indexer._upsert_regular_file (src/basic_memory/indexing/batch_indexer.py, ~line 441) computes a permalink and discards it:
if existing is None:
await self.entity_service.resolve_permalink(file.path, skip_conflict_check=True) # result discarded
entity = Entity(
note_type="file",
file_path=file.path,
... # no permalink= (correct — see below)
)
resolve_permalink is read-only (conflict detection + lookups, returns a string, no persistence side effect), and with skip_conflict_check=True it doesn't even emit the conflict warning. So this is a wasted DB round-trip per regular file during indexing, and it misleads readers into thinking the value was meant to be stored. Remove it, or comment why it's there. Check sync_service for the same pattern.
2. The invariant is undocumented. "Non-markdown entity ⇒ no permalink" holds in practice but is nowhere stated, so consumers keep rediscovering it the hard way — one that assumed a non-null permalink previously produced a hard failure when deleting a non-markdown file (fixed Cloud-side by making the delete/live-update contract permalink-optional). Document it, and keep consumer contracts explicitly permalink-optional.
Why NULL is correct
The permalink's durable home is the frontmatter; the DB row is a projection. A non-markdown file has nowhere to record one, so a DB-only permalink would be derived state that cannot round-trip:
- Unstable. Collisions resolve by suffixing (
-1, -1-1). With no file-side record, a rebuild-from-scratch regenerates permalinks in whatever order indexing happens — the same file could return with a different address, silently rotting any memory:// link or wikilink to it.
- Breaks rebuild-from-files. Drop the DB, reindex, get the same graph back; a permalink with no file-side record is the one field that wouldn't survive.
- Not needed. File entities already have two stable, file-derived addresses:
file_path and external_id. Permalink serves note semantics (memory:// addressing, wikilinks, search keying), which a .png doesn't have.
Supporting observation
In one sampled deployment the NULL-permalink set was exactly the files without frontmatter — 114 note_type=file plus 1 note_type=canvas (.canvas files are JSON; file_service assigns application/json) — while every markdown note_type had a permalink. The invariant is already consistent; only the documentation and the dead call are missing.
Explicitly not wanted
- Storing permalinks for regular files (see "Why NULL is correct").
- Backfilling existing NULL rows.
- Any change to permalink-keyed routing, search, or wikilink resolution.
Open question
Are non-markdown files excluded from full-text search because they lack a permalink, or by design independently of it? Observed but not determined — worth confirming so the search behavior is intentional rather than incidental.
Moved from the private Cloud tracker (basicmachines-co/basic-memory-cloud#1536), which holds the production evidence and the original failure report. Details omitted here because this repository is public.
What remains
1. A dead
resolve_permalink()call.batch_indexer._upsert_regular_file(src/basic_memory/indexing/batch_indexer.py, ~line 441) computes a permalink and discards it:resolve_permalinkis read-only (conflict detection + lookups, returns a string, no persistence side effect), and withskip_conflict_check=Trueit doesn't even emit the conflict warning. So this is a wasted DB round-trip per regular file during indexing, and it misleads readers into thinking the value was meant to be stored. Remove it, or comment why it's there. Checksync_servicefor the same pattern.2. The invariant is undocumented. "Non-markdown entity ⇒ no permalink" holds in practice but is nowhere stated, so consumers keep rediscovering it the hard way — one that assumed a non-null permalink previously produced a hard failure when deleting a non-markdown file (fixed Cloud-side by making the delete/live-update contract permalink-optional). Document it, and keep consumer contracts explicitly permalink-optional.
Why NULL is correct
The permalink's durable home is the frontmatter; the DB row is a projection. A non-markdown file has nowhere to record one, so a DB-only permalink would be derived state that cannot round-trip:
-1,-1-1). With no file-side record, a rebuild-from-scratch regenerates permalinks in whatever order indexing happens — the same file could return with a different address, silently rotting anymemory://link or wikilink to it.file_pathandexternal_id. Permalink serves note semantics (memory://addressing, wikilinks, search keying), which a.pngdoesn't have.Supporting observation
In one sampled deployment the NULL-permalink set was exactly the files without frontmatter — 114
note_type=fileplus 1note_type=canvas(.canvasfiles are JSON;file_serviceassignsapplication/json) — while every markdown note_type had a permalink. The invariant is already consistent; only the documentation and the dead call are missing.Explicitly not wanted
Open question
Are non-markdown files excluded from full-text search because they lack a permalink, or by design independently of it? Observed but not determined — worth confirming so the search behavior is intentional rather than incidental.
Moved from the private Cloud tracker (
basicmachines-co/basic-memory-cloud#1536), which holds the production evidence and the original failure report. Details omitted here because this repository is public.