openclaw - ✅(Solved) Fix Skills in dist-runtime/ skipped due to symlink realPath check [2 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

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…
GitHub stats
openclaw/openclaw#61585Fetched 2026-04-08 02:57:04
View on GitHub
Comments
0
Participants
1
Timeline
2
Reactions
0
Participants
Timeline (top)
cross-referenced ×1referenced ×1

Root Cause

dist-runtime/extensions/<plugin>/skills/<skill>/SKILL.md files are symlinks created by scripts/stage-bundled-plugin-runtime.mjs (stagePluginRuntimeOverlaysymlinkPath) pointing to dist/extensions/....

In src/agents/skills/workspace.ts, resolveContainedSkillPath calls fs.realpathSync() on the candidate path, which resolves the symlink to dist/extensions/.... It then checks isPathInside(rootRealPath, candidateRealPath), but rootRealPath is dist-runtime/extensions/<plugin>/skills (the directory itself is real, not a symlink). The resolved SKILL.md path (dist/...) is not inside dist-runtime/..., so the skill is rejected.

PR fix notes

PR #62382: fix: allow symlinked SKILL.md in bundled skills (dist-runtime)

Description (problem / solution / changelog)

Problem

In dist-runtime/extensions/, SKILL.md files are symlinks pointing to ../dist/extensions/.... Two separate containment checks rejected these symlinks:

  1. resolveContainedSkillPath in workspace.ts: Resolves symlinks via realpathSync and checks if the real path is inside the root's real path. Fails because dist/ is not inside dist-runtime/.

  2. readSkillFileSync in local-loader.ts: Uses rejectPathSymlink: true which rejects any symlinked SKILL.md outright. Additionally, isPathWithinRoot fails for the same reason as above.

Fix

workspace.ts

Added a logical-path fallback to resolveContainedSkillPath: when the realpath containment check fails and allowSymlinks is enabled, accept the path if:

  1. path.resolve(candidatePath) is logically inside rootDir (without symlink resolution)
  2. The resolved real path is inside allowedSymlinkTarget (the package root, 2 levels up from the bundled dir)

Both conditions must hold — the logical path check alone is insufficient as it could allow symlinks pointing to arbitrary locations.

local-loader.ts

Added allowSymlinks + allowedSymlinkTarget options flowing through loadSkillsFromDirSafeloadSingleSkillDirectoryreadSkillFileSync:

  • Disables rejectPathSymlink when symlinks are allowed
  • Falls back to dual containment check (logical path + resolved target boundary)

Security

  • allowSymlinks: true is only passed for bundled skills (trusted, part of the OpenClaw installation)
  • allowedSymlinkTarget constrains where resolved paths can land (package root)
  • Workspace skills and extra dirs retain strict symlink rejection
  • Test verifies both: symlinked bundled skills load, symlinked workspace skills are rejected
  • Existing escape-detection tests updated to use realistic directory structure matching the allowedSymlinkTarget boundary

Test Plan

  • Added src/agents/skills.symlink-containment.test.ts with 2 test cases
  • All 18 related tests pass (2 new + 16 existing loadWorkspaceSkillEntries)

Fixes #61585

Changed files

  • src/agents/skills.loadworkspaceskillentries.test.ts (modified, +13/-4)
  • src/agents/skills.symlink-containment.test.ts (added, +98/-0)
  • src/agents/skills/local-loader.ts (modified, +35/-3)
  • src/agents/skills/workspace.ts (modified, +50/-1)

PR #63312: Skills: allow symlinked skill dirs in user-managed .agents/skills/

Description (problem / solution / changelog)

Summary

  • Fix: resolveContainedSkillPath rejects symlinked skill directories inside ~/.agents/skills/ and project .agents/skills/ because their real (post-resolution) paths live outside the root directory. This is expected for user-managed sources — users and external tooling (e.g. obsidian-wiki setup.sh) commonly install skills via symlinks pointing elsewhere on disk.
  • Change: For agents-skills-personal and agents-skills-project sources, fall back to logical (pre-symlink-resolution) path containment when the physical check fails. System-managed sources (managed, bundled) keep the strict physical containment check unchanged.
  • Adds 3 test cases: symlinked personal skill, symlinked project skill, and a negative case confirming managed-dir symlinks are still rejected.

Fixes #61585

Test plan

  • pnpm test -- src/agents/skills.agents-skills-directory.test.ts — 6/6 pass (3 existing + 3 new)
  • pnpm test -- src/agents/skills — 34/34 pass across all 6 skill test files
  • pnpm tsgo — clean
  • pnpm format — clean (pre-existing tmp_aio_sandbox/ issues only)
  • Manual verification: create ~/.agents/skills/foo -> /external/path/foo/ symlink, confirm skill loads

Made with Cursor

Changed files

  • src/agents/skills.agents-skills-directory.test.ts (modified, +61/-0)
  • src/agents/skills/workspace.ts (modified, +12/-0)

Code Example

[skills] Skipping skill path that resolves outside its configured root.

---

{
  "source": "openclaw-extra",
  "rootDir": "/home/user/repo/openclaw/dist-runtime/extensions/feishu/skills",
  "path": "/home/user/repo/openclaw/dist-runtime/extensions/feishu/skills/feishu-wiki/SKILL.md",
  "realPath": "/home/user/repo/openclaw/dist/extensions/feishu/skills/feishu-wiki/SKILL.md"
}
RAW_BUFFERClick to expand / collapse

Bug

When running from source (node scripts/run-node.mjs gateway), the skill scanner logs warnings for every plugin-provided skill:

[skills] Skipping skill path that resolves outside its configured root.

This affects all skills under dist-runtime/extensions/ (feishu-doc, feishu-drive, feishu-perm, feishu-wiki, acp-router).

Root Cause

dist-runtime/extensions/<plugin>/skills/<skill>/SKILL.md files are symlinks created by scripts/stage-bundled-plugin-runtime.mjs (stagePluginRuntimeOverlaysymlinkPath) pointing to dist/extensions/....

In src/agents/skills/workspace.ts, resolveContainedSkillPath calls fs.realpathSync() on the candidate path, which resolves the symlink to dist/extensions/.... It then checks isPathInside(rootRealPath, candidateRealPath), but rootRealPath is dist-runtime/extensions/<plugin>/skills (the directory itself is real, not a symlink). The resolved SKILL.md path (dist/...) is not inside dist-runtime/..., so the skill is rejected.

Structured log evidence

{
  "source": "openclaw-extra",
  "rootDir": "/home/user/repo/openclaw/dist-runtime/extensions/feishu/skills",
  "path": "/home/user/repo/openclaw/dist-runtime/extensions/feishu/skills/feishu-wiki/SKILL.md",
  "realPath": "/home/user/repo/openclaw/dist/extensions/feishu/skills/feishu-wiki/SKILL.md"
}

Impact

  • 5 warnings per agent run (every incoming message)
  • In dev mode, skills still load via source paths, so functionality is not broken
  • In production (dist-only, no source tree), these skills would fail to load entirely

Possible fixes

  1. In resolveContainedSkillPath: also resolve rootDir through symlinks before comparing
  2. In stage-bundled-plugin-runtime.mjs: add .md files to shouldCopyRuntimeFile so SKILL.md gets copied instead of symlinked
  3. Both (belt and suspenders)

Option 2 seems safest and aligned with the existing pattern (package.json is already copied).

Environment

  • OpenClaw 2026.4.5, running from source on Linux
  • dist-runtime/ generated by pnpm build

extent analysis

TL;DR

Modify the stage-bundled-plugin-runtime.mjs script to copy SKILL.md files instead of symlinking them to resolve the skill scanner warnings.

Guidance

  • Identify the stage-bundled-plugin-runtime.mjs script and locate the shouldCopyRuntimeFile function to modify its behavior.
  • Update the shouldCopyRuntimeFile function to include .md files, ensuring SKILL.md files are copied instead of symlinked.
  • Verify the fix by running node scripts/run-node.mjs gateway and checking for the absence of skill scanner warnings.
  • Consider implementing the additional fix in resolveContainedSkillPath for extra robustness, but prioritize the stage-bundled-plugin-runtime.mjs modification as the primary solution.

Example

No explicit code example is provided due to the specific nature of the fix, but the modification should be applied to the shouldCopyRuntimeFile function in stage-bundled-plugin-runtime.mjs.

Notes

The provided solution focuses on modifying the stage-bundled-plugin-runtime.mjs script, as it seems to be the safest and most aligned approach with the existing pattern. However, the effectiveness of this fix may depend on the specific environment and OpenClaw version.

Recommendation

Apply the workaround by modifying the stage-bundled-plugin-runtime.mjs script to copy SKILL.md files, as it directly addresses the root cause of the issue and aligns with the existing pattern of copying package.json files.

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 - ✅(Solved) Fix Skills in dist-runtime/ skipped due to symlink realPath check [2 pull requests, 1 participants]