vault-wikilinks
Wikilink Integrity
Obsidian resolves [[Target]] by looking for a note whose basename is Target.md anywhere in the vault. Links break silently when a note is renamed, moved, or was never created. Ambiguity arises when two notes share a basename.
Link Syntax
[[Note Name]] # basename resolution
[[Note Name|Alias]] # custom display text
[[Note Name#Section heading]] # deep link to heading
[[folder/Note Name]] # path-qualified (usually unnecessary)
![[Image.png]] # embed (image, note, PDF)
Resolution Rules
- Unqualified target (
[[Docker]]) resolves to any note with basenameDocker.md. If two exist (e.g.Zettelkasten/Docker.mdandFVH/z/Docker.md), Obsidian picks one non-deterministically — ambiguous. - Path-qualified target (
[[Kanban/Main]]) resolves toKanban/Main.mdexactly — no basename fallback. - Embeds (
![[X]]) follow the same resolution. Image embeds typically target files underFiles/.
Common Breakage Patterns
| Pattern | Fix |
|---|---|
[[AnsibleFVH]] × many → note doesn't exist |
Rewrite to [[Ansible]] (the actual note) |
[[Development MOC]] → note was renamed |
Rewrite to [[Development Workflows and Tools MOC]] |
[[Kanban/X]] → works but path-qualified is brittle |
Rewrite to [[X]] when basename is unique |
[[code]], [[project]] → never were real notes |
These were inline-tag syntax errors; delete the link and leave plain text |
[[Gen AI Some Idea]] (double space) |
Fix the extra whitespace in the link |
Cross-Namespace Ambiguity
When two notes share a basename (e.g. Docker.md in both Zettelkasten/ and FVH/z/), every [[Docker]] in the vault becomes ambiguous. Options:
- Rename one so they stop colliding (
FVH/z/Docker.md→ keep as redirect stub; content lives inZettelkasten/Docker.md). - Path-qualify the links that should resolve to the non-canonical copy:
[[FVH/z/Docker]]. - Never use bare
[[Docker]]going forward; always path-qualify.
The preferred pattern is #1: keep canonical content in Zettelkasten/, make FVH/z/ a tiny redirect stub.
Detection
# Build a set of note basenames
fd -e md -x basename {} .md
# Find all wikilinks
rg -o '\[\[([^\]|#]+)' --no-filename --glob '*.md'
# Broken links: pipe the above through comm(1) against the basename set
A more accurate scan uses the links.analyze_links analyzer in vault-agent, which handles aliases, sections, and embeds correctly.
Rewriting Strategy
For a known-broken target with many references, rewrite in one commit:
fix(links): rewrite 44 × [[AnsibleFVH]] → [[Ansible]]
Use Edit with replace_all=True for the target string within each note. Don't use shell sed — it doesn't handle the frontmatter / codeblock boundary correctly, and Edit's per-file atomicity makes the commit review straightforward.
For small-count broken targets (1–2 references each), report them and let the user decide whether to delete the link, create the note, or redirect.
Ambiguous-Target Handling
Never auto-rewrite an ambiguous link. Report the ambiguity with both candidates and ask the user which resolution they want:
[[Docker]] in Zettelkasten/Kubernetes.md → candidates:
a) Zettelkasten/Docker.md
b) FVH/z/Docker.md (redirect stub)
Safety
- Never rewrite links inside code blocks or YAML frontmatter.
- Never auto-create missing target notes — that's a content decision, not a maintenance one.
- Preserve the alias form:
[[Ansible|my ansible]]→[[Ansible|my ansible]], not[[Ansible]].
Related Skills
- vault-orphans — notes with no links at all
- vault-mocs — structured outgoing-link hubs
- search-discovery — runtime link traversal via Obsidian CLI