Skip to content

fix[Bug]: [skills] Skipping skill path error triggered on officially installed skills via clawhub (WSL Environment)#59219

Open
luoxiao6645 wants to merge 1 commit intoopenclaw:mainfrom
luoxiao6645:fix/44051-managed-skills-realpath-scope-clean
Open

fix[Bug]: [skills] Skipping skill path error triggered on officially installed skills via clawhub (WSL Environment)#59219
luoxiao6645 wants to merge 1 commit intoopenclaw:mainfrom
luoxiao6645:fix/44051-managed-skills-realpath-scope-clean

Conversation

@luoxiao6645
Copy link
Copy Markdown

@luoxiao6645 luoxiao6645 commented Apr 1, 2026

Summary
Fix a managed-skills loading regression introduced by the recent skills realpath boundary hardening.

The loader started rejecting skill roots and SKILL.md files whose resolved realpath
escaped the configured source root. That behavior is correct for workspace and
extra-dir skills, but it was also being applied to managed skills under
~/.openclaw/skills.

As a result, managed skills installed via symlink/junction-backed layouts could be
skipped with:

Skipping skill path that resolves outside its configured root.

This surfaced in WSL reports, but the bug is in source scoping rather than
WSL-specific path normalization.

Root Cause
The containment check in src/agents/skills/workspace.ts was being enforced for
openclaw-managed as well as untrusted roots.

That meant a skill path that appeared under ~/.openclaw/skills/ but resolved
outside that directory via realpath was rejected, even though managed skill roots are
user-owned shared roots and were not intended to be subject to the same
workspace/extra-dir escape restriction.

What Changed
Added shouldEnforceContainedSkillPaths() to scope realpath boundary enforcement to:

  • openclaw-workspace
  • openclaw-extra
  • agents-skills-project
  • openclaw-bundled

Stopped applying that enforcement to managed/personal roots, while keeping bundled
roots fail-closed so stray checkout symlinks do not load out-of-root bundled skills.

Kept per-skill SKILL.md containment checks in place for all sources, so a skill dir
that is allowed to resolve externally still cannot load a SKILL.md that escapes its
own skill root.

Canonicalized loaded skill paths for sources exempt from root containment so managed
symlinked skills carry real paths into workspace sync/copy flows.

Kept size checks and normal loading behavior unchanged for trusted shared roots.

Added regression coverage for:

  • managed skill directories that resolve outside the managed root
  • managed SKILL.md symlinks that escape the skill root and must still be rejected
  • workspace escape cases that must remain fail-closed
  • bundled symlink escape warning behavior after merging latest main

Re-applied the follow-up fix that restores Claude bundle command loading in
buildWorkspaceSkillCommandSpecs().

Also fixed the pi-embedded-helpers.js partial mock issue that was blocking the
node test shard.

Files

  • src/agents/skills/workspace.ts
  • src/agents/skills.loadworkspaceskillentries.test.ts
  • src/agents/skills.build-workspace-skills-prompt.syncs-merged-skills-into-target-workspace.test.ts
  • src/commands/doctor-bootstrap-size.test.ts
  • src/agents/pi-embedded-runner/compact.hooks.harness.ts
  • src/agents/pi-embedded-runner/run.overflow-compaction.harness.ts

Test
Passed:

`pnpm exec vitest run src/agents/skills.loadworkspaceskillentries.test.ts src/agents/skills.buildworkspaceskillsnapshot.test.ts src/agents/skills.build-workspace-skills-
prompt.syncs-merged-skills-into-target-workspace.test.ts src/agents/skills.build-workspace-skills-prompt.prefers-workspace-skills-managed-skills.test.ts src/agents/skills-
status.test.ts

Real behavior proof

  • Behavior or issue addressed: Managed skills installed through a symlink/junction-backed ClawHub-style layout were being skipped because the loader enforced the configured managed root realpath as a containment boundary. After the patch, the managed skill directory can resolve outside the managed root while the skill's own SKILL.md containment remains enforced.
  • Real environment tested: Local OpenClaw checkout on Windows 11, Node via the repo toolchain, using the real loadWorkspaceSkillEntries() implementation from this branch. This models the WSL/ClawHub failure shape with a managed skills directory entry whose realpath points to an external cache-style skill directory.
  • Exact steps or command run after this patch: Ran a real Node/OpenClaw loader script from the repo root. The script created a temporary workspace, created a managed skills directory, created an external clawhub-cache/calendar-skill/SKILL.md, linked managed-skills/calendar-skill to that external directory, then called loadWorkspaceSkillEntries() with that managed directory.
  • Evidence after fix: Terminal output from the real loader run:
platform=win32
workspaceDir=C:\Users\13485\AppData\Local\Temp\openclaw-real-skill-proof-pY0TQq\workspace
managedLink=C:\Users\13485\AppData\Local\Temp\openclaw-real-skill-proof-pY0TQq\managed-skills\calendar-skill
managedLinkRealpath=C:\Users\13485\AppData\Local\Temp\openclaw-real-skill-proof-pY0TQq\clawhub-cache\calendar-skill
loadedSkills=browser-automation,calendar-skill,qqbot-channel,qqbot-media,qqbot-remind
loadedBaseDir=C:\Users\13485\AppData\Local\Temp\openclaw-real-skill-proof-pY0TQq\clawhub-cache\calendar-skill
result=PASS managed symlinked skill loaded
  • Observed result after fix: The real loader returned calendar-skill in loadedSkills, and loadedBaseDir was the external realpath target. This is the behavior users need for ClawHub/managed skill installs that use symlink or junction layouts; the skill was not skipped by the managed-root containment warning path.
  • What was not tested: I did not run a full WSL distro with the official ClawHub CLI in this session; the real runtime proof exercises the same OpenClaw managed skill loader path and symlink/junction realpath shape locally.

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: M labels Apr 1, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 1, 2026

Greptile Summary

This PR fixes a regression introduced by the skills realpath boundary hardening in 2026.3.9, where managed skills installed via symlink/junction-backed layouts (e.g. clawhub on WSL) were incorrectly rejected with "Skipping skill path that resolves outside its configured root."

Root cause fix: The new shouldEnforceContainedSkillPaths(source) function scopes the realpath containment checks to "openclaw-workspace", "openclaw-extra", and "agents-skills-project" sources only, leaving "openclaw-managed", "openclaw-bundled", and "agents-skills-personal" free to resolve skill roots via symlinks that point outside their parent directory.

Security boundary maintained: The new resolveContainedPathWithinSkillRoot helper is still called for all sources when checking whether a skill's SKILL.md resides inside its own skill directory (not the broader source root). This preserves the protection against a skill dir that is itself legitimately symlinked somewhere else but whose SKILL.md is then further symlinked to an arbitrary location.

Test coverage:

  • Regression case: managed skill dir symlinked outside managed root now loads successfully.
  • The old "skips workspace skill directories" test was gated to non-Windows; it's now split into a cross-platform version (using the symlinkDir helper, which falls back to junctions on Windows) and a separate file-symlink test that remains gated since file symlinks still require elevated privileges on Windows.
  • A new test verifies that a managed skill whose SKILL.md is further symlinked outside its own dir is still blocked.

Confidence Score: 5/5

  • Safe to merge — the fix is minimal, well-reasoned, and the security boundary for workspace/extra sources is fully preserved.
  • All remaining findings are P2. The core logic is correct: containment enforcement is now scoped to the untrusted workspace/extra/project roots, while the per-skill-dir SKILL.md check still guards all sources against deeper symlink traversal. Tests cover the regression scenario, the kept workspace security case, and the edge case of a doubly-symlinked SKILL.md in a managed skill.
  • No files require special attention.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/skills/workspace.ts
Line: 406-416

Comment:
**`skillDirRealPath` unused after guard**

When `enforceContainedRealpath` is `true`, `resolveContainedSkillPath` resolves and returns the real path of `skillDir`, but that resolved path is assigned to `skillDirRealPath` and then immediately discarded — neither the `filterLoadedSkillsInsideRoot` call nor any later code uses `skillDirRealPath`. The pre-existing code did the same thing, so this isn't a regression, but now that the block is visually isolated it's easier to see that the variable serves only as a guard (non-null == "path is inside root"). A `void` or simple boolean pattern would make the intent clearer, though this is not functionally wrong.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix: re-add loadEnabledClaudeBundleComma..." | Re-trigger Greptile

Re-review progress:

Comment thread src/agents/skills/workspace.ts
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bef96da8c9

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/skills.loadworkspaceskillentries.test.ts Outdated
@openclaw-barnacle openclaw-barnacle Bot added the app: macos App: macos label Apr 10, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 414d5ae529

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/skills/workspace.ts Outdated
@contentfree
Copy link
Copy Markdown

Any chance this "Error: [vitest] No "sanitizeUserFacingText" export is defined on the "../pi-embedded-helpers.js" mock. Did you forget to return it from "vi.mock"?" can get addressed so this PR can be merged? It'll really help to cut down on noisy logs.

@openclaw-barnacle openclaw-barnacle Bot added commands Command implementations and removed app: macos App: macos labels Apr 17, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dd30cd7792

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/skills/workspace.ts
@luoxiao6645
Copy link
Copy Markdown
Author

Addressed.

This PR now includes the follow-up fixes that were needed to get the branch back into a mergeable state after drift with current main:

  • fixed the noisy Vitest mock error by updating partial pi-embedded-helpers.js mocks to inherit real exports via vi.importActual(...), so newer exports like
    sanitizeUserFacingText are preserved
  • merged latest main and resolved the conflict in src/agents/skills.loadworkspaceskillentries.test.ts while keeping both the newer warning/home-env coverage and this PR’s
    managed-symlink regression coverage
  • restored bundled skill roots to containment enforcement so bundled symlink escapes remain fail-closed, while still leaving managed/personal roots outside that root-level
    enforcement

Relevant commits:

  • 3d44f1053b test: preserve pi-embedded helper exports in mocks
  • dd30cd7792 Merge upstream/main into fix/44051-managed-skills-realpath-scope-clean
  • ec9aed9b5d fix(skills): re-enforce bundled skill path containment

@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented May 1, 2026

Codex review: needs maintainer review before merge.

Summary
The PR scopes skill realpath containment by source, canonicalizes exempt managed/personal skill paths, adds regression coverage and a changelog entry, and updates related pi-embedded test mocks/expectations.

Reproducibility: yes. A high-confidence source reproduction is to put a symlink or junction-backed skill under the managed skills root; current main sends openclaw-managed through the same resolveContainedSkillPath() child-directory check and skips the skill when its realpath leaves that root.

Real behavior proof
Sufficient (terminal): The PR body supplies after-fix terminal output from a real Windows loader run showing a managed symlinked skill loaded from its external realpath target; the official WSL ClawHub CLI was not run, but the same OpenClaw loader path is exercised.

Next step before merge
No automated repair is needed; the latest head addresses prior blockers and should proceed through maintainer review plus required exact-head CI.

Security
Cleared: The diff touches a security-sensitive path boundary, but the latest code keeps untrusted root containment and per-skill SKILL.md containment, with no dependency, workflow, lockfile, or release-script changes.

Review details

Best possible solution:

Land this PR or an equivalent narrow patch after exact-head CI, preserving managed/personal root relaxation while keeping per-skill, workspace, extra, project, and bundled containment guards.

Do we have a high-confidence way to reproduce the issue?

Yes. A high-confidence source reproduction is to put a symlink or junction-backed skill under the managed skills root; current main sends openclaw-managed through the same resolveContainedSkillPath() child-directory check and skips the skill when its realpath leaves that root.

Is this the best way to solve the issue?

Yes. The PR fixes the source-scoping policy directly instead of adding WSL-specific path handling, while preserving the security-sensitive SKILL.md containment and fail-closed behavior for workspace, extra, project, and bundled roots.

Acceptance criteria:

  • Wait for required exact-head CI checks on ef4c81c.
  • Targeted validation already claimed by the PR: pnpm exec vitest run src/agents/skills.loadworkspaceskillentries.test.ts src/agents/skills.buildworkspaceskillsnapshot.test.ts src/agents/skills.build-workspace-skills-prompt.syncs-merged-skills-into-target-workspace.test.ts src/agents/skills.build-workspace-skills-prompt.prefers-workspace-skills-managed-skills.test.ts src/agents/skills-status.test.ts

What I checked:

  • Current main source-reproduces the skip: On current main, loadSkillEntries() calls resolveContainedSkillPath() for every source root and every child skill directory, including openclaw-managed, so a managed child whose realpath points outside the managed root is skipped. (src/agents/skills/workspace.ts:510, 99b17263a12d)
  • Docs support the intended boundary split: The skills docs list personal and managed roots as shared user-owned locations, and the security section says workspace and extra-dir discovery are the roots that must stay realpath-contained. Public docs: docs/tools/skills.md. (docs/tools/skills.md:44, 99b17263a12d)
  • PR narrows containment by source: The PR adds shouldEnforceContainedSkillPaths() for bundled, extra, workspace, and project-agent sources, while managed and personal roots use realpaths without root-level rejection; all sources still check each SKILL.md inside its own skill root. (src/agents/skills/workspace.ts:301, ef4c81cadc1d)
  • Regression coverage covers the intended behavior and guardrails: The PR tests loading a managed symlinked skill, rejecting managed SKILL.md symlink escapes, preserving workspace and bundled escape rejection, and dereferencing managed symlinks before sandbox/workspace sync. (src/agents/skills.loadworkspaceskillentries.test.ts:435, ef4c81cadc1d)
  • Real behavior proof is present: The PR body includes after-fix terminal output from a real Windows loader run showing calendar-skill loaded from an external realpath target through a managed symlink/junction-style layout, and the exact-head Real behavior proof check succeeded. (ef4c81cadc1d)
  • Related issue context confirms the bug remains relevant: The linked bug report at [Bug]: [skills] Skipping skill path error triggered on officially installed skills via clawhub (WSL Environment) #44051 describes the managed ClawHub skill skip, discussion identifies the same source-scoping root cause, and a later comment reports the same shape still happening on OpenClaw 2026.5.4 outside WSL.

Likely related people:

  • steipete: Introduced the workspace skill path containment hardening and has multiple recent commits around escaped skill warnings, filesystem safety primitives, and skills tests/docs. (role: introduced behavior and recent maintainer; confidence: high; commits: 253e15970059, e24b80b15e46, 538605ff44d2; files: src/agents/skills/workspace.ts, src/agents/skills.loadworkspaceskillentries.test.ts, docs/tools/skills.md)
  • vincentkoc: Recent path history includes personal-skill home handling, grouped-skill scan caps, and skills documentation work that overlaps the source precedence and discovery surface touched here. (role: adjacent owner and recent maintainer; confidence: medium; commits: 8ae6d42faabf, a093b5b2de98, 2b2959461181; files: src/agents/skills/workspace.ts, src/agents/skills.loadworkspaceskillentries.test.ts, docs/tools/skills.md)
  • pgondhi987: Authored the symlink-safe, root-confined local skill file loader work that underpins the per-skill SKILL.md containment this PR must preserve. (role: root-confined loader contributor; confidence: medium; commits: b7b46ad18539; files: src/agents/skills/local-loader.ts, src/agents/skills/workspace.ts)
  • ottodeng: Added grouped skill directory discovery, which this PR explicitly preserves while changing managed root containment behavior. (role: adjacent owner; confidence: medium; commits: 8ca1f6d590c8; files: src/agents/skills/workspace.ts, src/agents/skills.loadworkspaceskillentries.test.ts, docs/tools/skills.md)

Remaining risk / open question:

  • Several exact-head CI shards were still queued at inspection time; merge should wait for required checks to complete.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 99b17263a12d.

@luoxiao6645
Copy link
Copy Markdown
Author

I ported the fix onto current main and kept the current grouped-skill discovery path intact. The loader now only relaxes root-level realpath containment for managed/
personal skill roots, while workspace, extra, project-agent, and bundled roots remain fail-closed. Per-skill SKILL.md containment still applies for all sources, including
managed symlink/junction-backed skills.

Also added the required CHANGELOG.md entry for #44051 / #59219.

Additional cleanup:

  • Added regression coverage for managed symlink/junction skill directories.
  • Preserved grouped skills such as skills///SKILL.md.
  • Kept workspace and bundled symlink escape behavior covered.
  • Fixed the compact harness expectation drift on Windows after the current-main merge

@luoxiao6645 luoxiao6645 force-pushed the fix/44051-managed-skills-realpath-scope-clean branch from 870393d to ef4c81c Compare May 6, 2026 15:16
@openclaw-barnacle openclaw-barnacle Bot added triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 6, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling commands Command implementations proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants