Skip to content

memory-wiki lint: wikilink extractor is not markdown-aware; resolver never matches frontmatter titles #70986

Description

@richardmqq

Summary

openclaw wiki lint produces two classes of false/unfixable "Broken wikilink target" warnings that are rooted in the wikilink extractor and link resolver implementations themselves, not in vault content. After a live vault audit (~150 pages, 84 warnings), roughly all warnings fall into these two buckets:

  1. Parser is markdown-blind — literal [[...]] inside prose/code/transcripts is extracted as a wikilink target, even when it is clearly documentation syntax, an OpenClaw directive tag, or a quoted transcript.
  2. Resolver only indexes slugified path + basename, never frontmatter title — natural-title wikilinks like [[Codex harness plugin 配置清单]] can never resolve, because the resolver's validTargets set only contains post-slugification forms.

These are upstream bugs in the memory-wiki plugin, independent of #64696 (which proposes tag-specific sanitization for [[reply_to_*]]). The fix scope proposed below targets the root causes, which would also cover #64696 without per-tag bandaids.

Installed version: openclaw 2026.4.22 (commit 00bd2cf).

Evidence

Class 1 — parser false positives on literal [[...]]

Sample warnings from Wiki/reports/lint.md:

  • Broken wikilink target `tts:text`.
  • Broken wikilink target `reply_to:<id>`.
  • Broken wikilink target `reply_to_current`.
  • Broken wikilink target `wikilinks`.
  • Broken wikilink target `...`.

Concrete example — reply_to_current occurs 8 times in this one bridged source page, always in plain prose inside transcript text, not as a real wikilink:

Wiki/sources/bridge-workspace-system-architect-1ca28ce0-memory-2026-04-19-provider-name-c3ee55a4 2.md
line 34:  assistant: [[reply_to_current]]
line 124: assistant: [[reply_to_current]]
line 218: assistant: [[reply_to_current]]
...

These are literal OpenClaw reply directives that were captured verbatim when the session transcript was bridged into the vault. Similar patterns occur for [[tts:text]], [[reply_to:<id>]], and for markdown meta-discussions where users literally write [[wikilinks]] or [[...]] as documentation syntax. [[Omitted long matching line]] (system truncation hint) also gets extracted.

Class 2 — resolver cannot match wikilinks written by natural title

All four of the following are real, on-disk, indexed source pages whose frontmatter title exactly matches the [[...]] target — but lint flags them as broken:

Wikilink in body Target file that exists Frontmatter title
[[Codex harness plugin 配置清单]] Wiki/sources/codex-harness-plugin-配置清单.md (bridged from OpenClaw/GPT 5 系列/Codex harness plugin 配置清单.md) Codex harness plugin 配置清单
[[OpenClaw Agent 体系深度分析 — openclaw-agent-system]] Wiki/sources/openclaw-agent-体系深度分析.md OpenClaw Agent 体系深度分析
[[深度解析 OpenClaw Prompt Context Harness]] Wiki/sources/深度解析-openclaw-prompt-context-harness.md (verified on-disk) 深度解析 OpenClaw Prompt Context Harness
[[OpenClaw × Claude Code × Codex 共享 Memory 方案]] slugified filename drops the × characters OpenClaw × Claude Code × Codex 共享 Memory 方案

Frontmatter of the first target, verified via head:

---
pageType: source
id: source.codex-harness-plugin-配置清单
title: Codex harness plugin 配置清单
sourceType: local-file
sourcePath: /…/OpenClaw/GPT 5 系列/Codex harness plugin 配置清单.md
---

Users naturally write [[Codex harness plugin 配置清单]]. The resolver never matches, because its validTargets set only contains sources/codex-harness-plugin-配置清单 and codex-harness-plugin-配置清单 — the slugified forms.

Root cause (verified in installed source)

1. Parser is not markdown-aware

/opt/homebrew/lib/node_modules/openclaw/dist/cli-BF_4kd_o.js:

// line 180
const OBSIDIAN_LINK_PATTERN = /\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g;

// line 273
function extractWikiLinks(markdown) {
  const searchable = markdown.replace(RELATED_BLOCK_PATTERN, "");
  const links = [];
  for (const match of searchable.matchAll(OBSIDIAN_LINK_PATTERN)) {
    const target = match[1]?.trim();
    if (target) links.push(target);
  }
  
}

The regex is run directly against the raw document minus only the openclaw:wiki:related managed block. It does not skip:

  • fenced code blocks (```` ``` `)
  • inline code (`)
  • YAML frontmatter
  • HTML comments
  • Obsidian callouts with code examples

Any document that talks about OpenClaw directives, Obsidian wikilink syntax, or session transcripts will emit spurious targets. Known Issue tts-parser-markdown-blind (our internal tracker) describes the exact same class of bug in the TTS directive parser — this is the wiki parser's sibling.

2. Resolver only knows slugified paths

Same bundle, lines ~2286–2300:

function collectBrokenLinkIssues(pages) {
  const validTargets = new Set();
  for (const page of pages) {
    const withoutExtension = page.relativePath.replace(/\.md$/i, "");
    validTargets.add(withoutExtension);                // "sources/codex-harness-plugin-配置清单"
    validTargets.add(path.basename(withoutExtension)); // "codex-harness-plugin-配置清单"
  }
  const issues = [];
  for (const page of pages)
    for (const linkTarget of page.linkTargets)
      if (!validTargets.has(linkTarget))
        issues.push({ severity: "warning", category: "links", code: "broken-wikilink",});
  return issues;
}

validTargets never contains page.title, so [[<natural title>]] is guaranteed to miss whenever the stored file has been slugified (which is the default for ingested Chinese-title content, any filename with special characters like ×, or long-title files). formatWikiLink() emits Obsidian-style [[slug|Title]] aliases, which the extractor correctly strips via its (?:\|[^\]]+)? group — but anything the user or Librarian writes as [[Title]] bare is unresolvable.

Expected behavior

  1. The wikilink extractor should respect Markdown / MDX structure and not extract [[...]] tokens that appear inside:
    • fenced code blocks ( and ~~~)
    • inline code (`…`)
    • YAML frontmatter
    • HTML comments
  2. The resolver should additionally accept:
    • a match against page.title (case-insensitive, or case-sensitive + normalized whitespace)
    • optionally, a match after the user's stored slug rule is applied to the extracted target (so both [[Codex harness plugin 配置清单]] and [[codex-harness-plugin-配置清单]] resolve to the same page)

Proposed fix scope

  • Parser: strip fenced code blocks, inline code, and HTML comments from searchable before the wikilink regex runs. The YAML frontmatter is already addressed upstream; fenced / inline code are the main holes.
  • Resolver: build validTargets as a union of {relativePath sans .md, basename, frontmatter.title, slugify(frontmatter.title), any declared aliases}; compare extracted target against all of them using a normalization function (lowercase, collapse whitespace, unify /-).
  • Docs: if wikilinks by slug remain a blessed form, note it explicitly; but title-based resolution is what every existing Obsidian-vault user expects and what OpenClaw's own formatWikiLink alias rendering implies.

Related

Markdown-awareness in the extractor is the root-cause fix that subsumes the per-tag approach.

Reproduction

  1. In a bridge-mode vault, bridge a session transcript that contains the string [[reply_to_current]] literally in assistant output.
  2. Create sources/example-配置清单.md with frontmatter title: Example 配置清单.
  3. In another page, link it with [[Example 配置清单]].
  4. Run openclaw wiki lint.

Both lines will appear as Broken wikilink target ….

Acceptance criteria

  • openclaw wiki lint does not flag [[…]] that sits inside a fenced code block or inline code span.
  • openclaw wiki lint resolves [[<title>]] when any indexed page's frontmatter title equals the target (normalization policy documented).
  • Vaults upgrading from the current implementation stop seeing false-positive Broken wikilink target for OpenClaw directive tags that appear in bridged transcripts, without requiring a tag-specific allowlist.

Environment

  • OpenClaw: 2026.4.22 (00bd2cf), macOS 15.7.3
  • Plugin: dist/extensions/memory-wiki (bundled)
  • Vault: bridge mode, Obsidian render mode, 150 pages

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:linked-pr-openClawSweeper found an open linked pull request for this issue.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.staleMarked as stale due to inactivity

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions