fix(core): resolve Markdown path links against the note at resolution time - #1577
Merged
Merged
Conversation
… time A Markdown link was the only relation whose meaning depended on where the parser read the bytes: the parser derived the note's project path from the filesystem path with relative_to and turned the href into a rooted path at parse time. Content parsed from anywhere outside the project root raised (cloud reads a note from object storage into a temp file), and any other temporary location gave every link the wrong base. Wikilinks are recorded as authored and resolved later against database state. Path links now behave the same way: - The parser stores the path as the author wrote it: ../guides/Guide.md, ./same.md (a bare same.md gets the ./ mark so the stored target says it is a path and not a title), or a rooted /root.md. parse() takes no source path. - Both resolvers detect a path target (/, ./, ../) after wikilink normalization and resolve it against the note's own project path with resolve_project_path. Exact file only; no title, permalink, alias or cross-project fallback; a path that climbs past the root names nothing. Path-shaped wikilinks such as [[../x.md]] follow the same rule. - Background resolution keys targets by RelationTargetRequest(link_text, source_path). Identity targets carry no source and resolve once for every note; path targets are keyed by their source note, so ./Guide.md from two folders resolves to two files in one pass. Source paths are loaded with one find_by_ids call only when a batch contains a path target. - The write-time self-link check resolves the authored path against the note's own path. Existing relation rows for Markdown links are rewritten to the authored form on the note's next edit or reindex, as the docs already state. Refs #1514 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019YW9ysxugGGBCNEGzsxtFV Signed-off-by: phernandez <paul@basicmachines.co>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
phernandez
deleted the
fix/1514-path-links-resolve-at-resolution-time
branch
September 16, 2026 03:44
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the #1514 follow-up where Markdown path links were resolved at parse time from the file's location on disk. Cloud's
S3FileService.read_entity_contentparsed a note from a temp file outside the project root,Path.relative_toraised, and the read failed (cloud main CI red; cloud side fixed in basicmachines-co/basic-memory-cloud#2086).The root cause was in core: a Markdown link was the only relation whose meaning depended on where the parser read the bytes. Wikilinks are recorded as authored and resolved later against database state. This PR makes path links behave the same way, and gives path-shaped wikilinks (
[[../x.md]],[[./x.md]]) the same exact-file resolution.What changes
markdown/entity_parser.py,markdown/path_links.py):parse(content)no longer takes asource_path. A Markdown link is stored as the path its author wrote:../guides/Guide.md,./same.md(a baresame.mdgets the./mark so the stored target says it is a path, not a title), or a rooted/root.md. URLs,mailto:/file:links, fragment-only links, backslash or null-byte paths, and folder hrefs (./,docs/) create no relation.services/link_resolver.py,services/bulk_link_resolver.py): both resolvers detect a path target (/,./,../) after wikilink normalization and resolve it withresolve_project_path(target, source_path)against the note's own project path. Exact file only. No title, permalink, alias, or cross-project fallback. A path that climbs past the project root resolves to nothing.indexing/relation_resolution.py,indexing/models.py): the batch resolver contract now takesRelationTargetRequest(link_text, source_path). Identity targets carry no source and resolve once for every note; path targets are keyed by their source note, so./Guide.mdfroma/and fromb/resolve to two different files in one pass. Source paths are fetched with onefind_by_idscall only when the batch contains a path target.services/note_preparation.py): the write-time self-link check resolves the authored path against the note's own path, so[self](Self.md)innotes/Self.mdstill resolves to itself.docs/MARKDOWN_RELATIONS.mddescribes the stored form and resolution rule;CHANGELOG.mdgets a Bug Fixes entry.Behaviour change for stored data
Relation rows for Markdown links now store the authored path instead of the parser-resolved rooted path. Existing rows are rewritten on the note's next edit or reindex, as the docs already state for these relations. Unresolved rooted rows (
/x.md) from before this change resolve the same way they did.Tests
tests/markdown/test_path_links.py: tables formarkdown_link_path,resolve_project_path,is_path_target; parser test proves the authored form is stored and that parsing needs no filesystem location.tests/services/test_markdown_path_resolution.py: rooted,./,../, no-source, past-root, and[[../x.md|alias]]cases throughLinkResolver.tests/services/test_bulk_link_resolver.py: the same authored path resolves to a different file from each source note; a path never falls back to a title.tests/indexing/test_relation_resolution.py: path targets keyed per source note in one batch, identity targets still deduplicated.test-int/mcp/test_markdown_path_relations_integration.py: end-to-end throughwrite_note, including a path-shaped wikilink and a self-link.Verification run locally:
Cloud follow-up
After this merges, cloud re-pins
basic-memoryto the new SHA. The in-memory parse in cloud #2086 stays correct either way; with this change the parser no longer needs a project-relative path at all.Closes the parse-time half of #1514.
🤖 Generated with Claude Code
https://claude.ai/code/session_019YW9ysxugGGBCNEGzsxtFV