openclaw - 💡(How to fix) Fix memory-wiki: "Refusing to write imported source page through symlink" fires for non-symlink FsSafeError causes [2 pull requests]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

wiki_status (and any bridge import write) reports Refusing to write imported source page through symlink: <path> even when no symlink exists anywhere on the page path. The catch in writeImportedSourcePage re-wraps every FsSafeError with a "through symlink" message, hiding the real cause (in my case, a restrictive-permissions/atomic-replace failure on a bridge-imported page written at mode 600).

Error Message

} catch (error) { if (error instanceof FsSafeError) { throw new Error( { cause: error }, throw error; Result: Error: Refusing to write imported source page through symlink: wiki/main/sources/<page>.md — even though realpath of every component shows no symlink.

  • The error message reflects the actual FsSafeError.code (e.g. Refusing to write imported source page (perms-denied): <path>), or if (error instanceof FsSafeError) { error.code === "symlink-parent" || error.code === "path-alias" : (${error.code}); throw new Error( { cause: error },

Root Cause

  1. Run a wiki bridge import that produces an imported source page (e.g. memory bridge from another workspace).
  2. Ensure the resulting page file ends up at mode 600 in an otherwise normal (non-symlinked) directory tree. In my case this happened on first write because the source file (workspace-bulletin/memory/.dreams/events.jsonl) was 600 and the writer inherited restrictive perms.
  3. Trigger a re-render of that page (e.g. wiki_status, which re-runs imported-source writes).

Fix Action

Fixed

Code Example

try {
  if (pageStat && pageStat.nlink > 1) {
    await vault.remove(params.pagePath);
  }
  await vault.write(params.pagePath, rendered);
} catch (error) {
  if (error instanceof FsSafeError) {
    throw new Error(
      `Refusing to write imported source page through symlink: ${params.pagePath}`,
      { cause: error },
    );
  }
  throw error;
}

---

if (error instanceof FsSafeError) {
  const reason =
    error.code === "symlink-parent" || error.code === "path-alias"
      ? "through symlink"
      : `(${error.code})`;
  throw new Error(
    `Refusing to write imported source page ${reason}: ${params.pagePath}`,
    { cause: error },
  );
}
RAW_BUFFERClick to expand / collapse

Summary

wiki_status (and any bridge import write) reports Refusing to write imported source page through symlink: <path> even when no symlink exists anywhere on the page path. The catch in writeImportedSourcePage re-wraps every FsSafeError with a "through symlink" message, hiding the real cause (in my case, a restrictive-permissions/atomic-replace failure on a bridge-imported page written at mode 600).

Where

extensions/memory-wiki/src/source-page-shared.ts, in writeImportedSourcePage:

try {
  if (pageStat && pageStat.nlink > 1) {
    await vault.remove(params.pagePath);
  }
  await vault.write(params.pagePath, rendered);
} catch (error) {
  if (error instanceof FsSafeError) {
    throw new Error(
      `Refusing to write imported source page through symlink: ${params.pagePath}`,
      { cause: error },
    );
  }
  throw error;
}

FsSafeError covers a family of codes (invalid-path, outside-workspace, path-alias, plus the symlink-parent / perms hardening from #66636, #69797/8, #80331). Any of them surface as "through symlink", which is misleading.

extensions/memory-wiki/src/bridge.test.ts asserts on the exact "through symlink" string, so changing the message has a test impact — flagging in case maintainers want to decide direction.

Repro

  1. Run a wiki bridge import that produces an imported source page (e.g. memory bridge from another workspace).
  2. Ensure the resulting page file ends up at mode 600 in an otherwise normal (non-symlinked) directory tree. In my case this happened on first write because the source file (workspace-bulletin/memory/.dreams/events.jsonl) was 600 and the writer inherited restrictive perms.
  3. Trigger a re-render of that page (e.g. wiki_status, which re-runs imported-source writes).

Result: Error: Refusing to write imported source page through symlink: wiki/main/sources/<page>.md — even though realpath of every component shows no symlink.

In my case:

  • Path: wiki/main/sources/bridge-workspace-bulletin-075c90ca-memory-dreams-events-jsonl-38a7dd37.md
  • Mode: -rw------- (only file in sources/ with that mode; all others 664)
  • Verified: no symlink on any ancestor.

Expected

Either:

  • The error message reflects the actual FsSafeError.code (e.g. Refusing to write imported source page (perms-denied): <path>), or
  • The catch is tightened to wrap only the symlink-parent / path-alias codes with the "through symlink" message and lets other FsSafeErrors bubble with their own message.

Either way: surface the real reason so users can fix it (in my case, chmod 644 resolves it instantly once you know what's happening).

Sketch fix

if (error instanceof FsSafeError) {
  const reason =
    error.code === "symlink-parent" || error.code === "path-alias"
      ? "through symlink"
      : `(${error.code})`;
  throw new Error(
    `Refusing to write imported source page ${reason}: ${params.pagePath}`,
    { cause: error },
  );
}

bridge.test.ts would need its assertion updated alongside.

Environment

  • OpenClaw: installed via npm i -g openclaw (whichever was current on 2026-05-18 NZT)
  • Host: WSL2 (Ubuntu) on Windows 11
  • Vault root: workspace bridge importing from a sibling workspace

Happy to send a PR if maintainers confirm the preferred direction (message rewrite vs. tightened catch).

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix memory-wiki: "Refusing to write imported source page through symlink" fires for non-symlink FsSafeError causes [2 pull requests]