Bug Description
build_context has two lookup paths that disagree. Passing a plain permalink resolves it literally and works. Passing the same permalink as a memory:// URI runs it through generate_permalink() first, and when the stored permalink is not already a fixed point of that function, the lookup misses and returns an empty result set with no error.
A stored permalink only survives the round trip if generate_permalink(p) == p. That is true for permalinks Basic Memory generated itself, and frequently false for permalinks set explicitly in a note's frontmatter, which are stored verbatim.
This came up with records keyed by opaque session identifiers like ses_f70d4bd30ffeovRRDRjHUkf61c, where the permalink has to match the identifier rather than a slug of the title. read_note and search_notes find those notes fine, so it looks like the note is present and only traversal is broken.
Steps To Reproduce
- Install 0.23.2
- Create a project and one note whose frontmatter sets a permalink containing an underscore and mixed case:
bm project add repro /tmp/repro
printf -- '---\npermalink: s/2026/09/ses_AbCdEfGhIj\n---\n\n# T\n\nBody.\n' \
| bm tool write-note --project repro --title index --folder "s/2026/09/ses_AbCdEfGhIj"
- Query it both ways:
bm tool build-context --project repro "memory://s/2026/09/ses_AbCdEfGhIj" --json
bm tool build-context --project repro "s/2026/09/ses_AbCdEfGhIj" --json
Expected Behavior
Both forms resolve the note, or the failing one reports that it could not find it.
Actual Behavior
memory://s/2026/09/ses_AbCdEfGhIj -> {"uri": "repro/s/2026/09/ses-ab-cd-ef-gh-ij", "primary_count": 0}
s/2026/09/ses_AbCdEfGhIj -> {"uri": "s/2026/09/ses_AbCdEfGhIj", "primary_count": 1}
The memory:// form reports success with zero results. The returned uri shows the rewrite: the underscore became a hyphen and the camelCase run was split. Note it also acquires a project-name prefix, which the working path does not, so the two paths differ in more than normalisation.
Lowercasing does not avoid it, which is worth stating because it is the obvious first guess:
generate_permalink('s/2026/09/ses_AbCdEfGhIj') -> 's/2026/09/ses-ab-cd-ef-gh-ij'
generate_permalink('s/2026/09/ses_abcdefghij') -> 's/2026/09/ses-abcdefghij'
generate_permalink('s/2026/09/ses-abcdefghij') -> unchanged
Environment
- OS: Ubuntu 25.10
- Python version: 3.14.2
- Basic Memory version: 0.23.2
- Installation method:
uv tool install
- Claude Desktop version: not applicable, used through OpenCode and the
bm CLI
Additional Context
Identical through the MCP tool and through bm tool build-context, so it is in the service layer rather than either front end.
The normalisation was added in #329 to fix #304, where a memory:// URI containing underscores failed against a permalink stored with hyphens. That is the same mismatch from the opposite side: being generous about under-normalised input helps, and hurts when the stored value is the non-normalised one. #416 was the equivalent follow-up for read_note.
Possible Solution
Two changes, either of which fixes it, and both seem worth having:
Try the literal path first and fall back to the normalised one only when the literal lookup finds nothing. That keeps #304 working and stops explicit permalinks from being unreachable.
Stop returning an empty success. #1148 and #1151 established that ambiguous strict identifier resolution should fail loudly rather than guess; a silent zero-result is the same objection from the other direction. An error naming both the requested and the normalised path would make this diagnosable in seconds instead of hours.
Bug Description
build_contexthas two lookup paths that disagree. Passing a plain permalink resolves it literally and works. Passing the same permalink as amemory://URI runs it throughgenerate_permalink()first, and when the stored permalink is not already a fixed point of that function, the lookup misses and returns an empty result set with no error.A stored permalink only survives the round trip if
generate_permalink(p) == p. That is true for permalinks Basic Memory generated itself, and frequently false for permalinks set explicitly in a note's frontmatter, which are stored verbatim.This came up with records keyed by opaque session identifiers like
ses_f70d4bd30ffeovRRDRjHUkf61c, where the permalink has to match the identifier rather than a slug of the title.read_noteandsearch_notesfind those notes fine, so it looks like the note is present and only traversal is broken.Steps To Reproduce
Expected Behavior
Both forms resolve the note, or the failing one reports that it could not find it.
Actual Behavior
The
memory://form reports success with zero results. The returnedurishows the rewrite: the underscore became a hyphen and the camelCase run was split. Note it also acquires a project-name prefix, which the working path does not, so the two paths differ in more than normalisation.Lowercasing does not avoid it, which is worth stating because it is the obvious first guess:
Environment
uv tool installbmCLIAdditional Context
Identical through the MCP tool and through
bm tool build-context, so it is in the service layer rather than either front end.The normalisation was added in #329 to fix #304, where a
memory://URI containing underscores failed against a permalink stored with hyphens. That is the same mismatch from the opposite side: being generous about under-normalised input helps, and hurts when the stored value is the non-normalised one. #416 was the equivalent follow-up forread_note.Possible Solution
Two changes, either of which fixes it, and both seem worth having:
Try the literal path first and fall back to the normalised one only when the literal lookup finds nothing. That keeps #304 working and stops explicit permalinks from being unreachable.
Stop returning an empty success. #1148 and #1151 established that ambiguous strict identifier resolution should fail loudly rather than guess; a silent zero-result is the same objection from the other direction. An error naming both the requested and the normalised path would make this diagnosable in seconds instead of hours.