Version
openclaw 2026.5.4 (325df3e)
- Windows 11 (build 26200), Node.js (via nvm4w), PowerShell 7
- Standard user account; Developer Mode is OFF
Summary
Plugin-skill registration uses fs.symlink to expose plugin-shipped skills under ~/.openclaw/plugin-skills/. On Windows, creating a fs.symlink requires either administrator elevation or Windows Developer Mode. For users with neither (the default), every plugin-shipped skill silently fails to register with EPERM: operation not permitted, symlink ....
In my install, this leaves at least three skills undiscovered: browser-automation (from @openclaw/browser-plugin), obsidian-vault-maintainer, and wiki-maintainer (both from @openclaw/memory-wiki). The errors also flood stderr on every openclaw doctor invocation — but the worse impact is that openclaw skills list doesn't show them at all, so users can't easily tell why an installed plugin's skills aren't available.
Reproduction
Fresh install on a standard Windows user account (no Developer Mode, no admin):
> openclaw doctor
...
[skills] failed to create plugin skill symlink "C:\Users\jarro\.openclaw\plugin-skills\browser-automation" → "C:\Users\jarro\AppData\Roaming\npm\node_modules\openclaw\dist\extensions\browser\skills\browser-automation": Error: EPERM: operation not permitted, symlink 'C:\Users\jarro\AppData\Roaming\npm\node_modules\openclaw\dist\extensions\browser\skills\browser-automation' -> 'C:\Users\jarro\.openclaw\plugin-skills\browser-automation'
[skills] failed to create plugin skill symlink "C:\Users\jarro\.openclaw\plugin-skills\obsidian-vault-maintainer" → "C:\Users\jarro\AppData\Roaming\npm\node_modules\openclaw\dist\extensions\memory-wiki\skills\obsidian-vault-maintainer": Error: EPERM: operation not permitted, symlink ...
[skills] failed to create plugin skill symlink "C:\Users\jarro\.openclaw\plugin-skills\wiki-maintainer" → "C:\Users\jarro\AppData\Roaming\npm\node_modules\openclaw\dist\extensions\memory-wiki\skills\wiki-maintainer": Error: EPERM: operation not permitted, symlink ...
The same errors print on every CLI invocation that touches plugin-skill registration.
Why a plain fs.symlink fallback is the wrong fix on Windows
On NTFS, directory junctions (reparse points) behave the same as symlinks for fs.readdir/fs.realpath and do not require elevation or Developer Mode. The skill folders OpenClaw is trying to expose are all directories rooted under the npm module dir, which is exactly the case junctions handle.
I confirmed locally that the same target the symlink call is failing on works fine via a junction:
> $src = "$env:APPDATA\npm\node_modules\openclaw\dist\extensions\browser\skills\browser-automation"
> $dst = "$env:TEMP\junction-test"
> cmd /c "mklink /J `"$dst`" `"$src`""
Junction created for C:\Users\jarro\AppData\Local\Temp\junction-test <<===>> C:\Users\jarro\AppData\Roaming\npm\node_modules\openclaw\dist\extensions\browser\skills\browser-automation
(No admin, no Developer Mode.)
Suggested fix
In the plugin-skill registration helper, on process.platform === "win32" and only for directory targets, fall back to a junction when fs.symlink throws EPERM:
try {
await fs.symlink(target, link, "junction"); // Node already supports "junction" type on Windows
} catch (err) {
if (process.platform === "win32" && (err as NodeJS.ErrnoException).code === "EPERM") {
// last-resort: spawn `cmd /c mklink /J` if the junction type didn't take
await spawnMklinkJunction(link, target);
} else {
throw err;
}
}
Node's own fs.symlink(target, path, "junction") on Windows already maps to a directory junction without requiring elevation, so depending on what's currently being passed, switching the type argument from default/"dir" to "junction" may be the entire fix. Worth confirming the call site is using the default/"dir" mode (which uses NT symlinks and triggers the EPERM).
If a junction is genuinely undesirable (e.g. the skill target is a file, not a directory), fall back to a hard copy — slower but guaranteed to work — and surface a one-line warning telling the user how to enable Developer Mode if they want symlink behavior back.
Impact
Three "stock" plugin-shipped skills are silently unavailable on stock Windows installs:
browser-automation — blocks the entire @openclaw/browser-plugin skill surface
obsidian-vault-maintainer
wiki-maintainer
I ship a desktop frontend on top of OpenClaw (Crystal: https://github.com/jvpflum/Crystal) and was tracking down "why does my browser skill list show empty" when I found this in openclaw doctor. The error is logged but never bubbles up to the skills surface, so users hit it as an "I installed the plugin but the skill isn't there" mystery.
Out of scope but related
The EPERM error itself is also printed to stderr on every CLI invocation that touches skill registration, including --json reads. That's the same anti-pattern flagged in #77942 (config-validator output leaking into machine-readable command stderr). Mentioning here for context but not asking for it to be fixed in this issue.
Happy to send a focused PR + test once you confirm the preferred call-site change (switch fs.symlink type to "junction" vs add explicit fallback logic).
Version
openclaw 2026.5.4 (325df3e)Summary
Plugin-skill registration uses
fs.symlinkto expose plugin-shipped skills under~/.openclaw/plugin-skills/. On Windows, creating afs.symlinkrequires either administrator elevation or Windows Developer Mode. For users with neither (the default), every plugin-shipped skill silently fails to register withEPERM: operation not permitted, symlink ....In my install, this leaves at least three skills undiscovered:
browser-automation(from@openclaw/browser-plugin),obsidian-vault-maintainer, andwiki-maintainer(both from@openclaw/memory-wiki). The errors also flood stderr on everyopenclaw doctorinvocation — but the worse impact is thatopenclaw skills listdoesn't show them at all, so users can't easily tell why an installed plugin's skills aren't available.Reproduction
Fresh install on a standard Windows user account (no Developer Mode, no admin):
The same errors print on every CLI invocation that touches plugin-skill registration.
Why a plain
fs.symlinkfallback is the wrong fix on WindowsOn NTFS, directory junctions (reparse points) behave the same as symlinks for
fs.readdir/fs.realpathand do not require elevation or Developer Mode. The skill folders OpenClaw is trying to expose are all directories rooted under the npm module dir, which is exactly the case junctions handle.I confirmed locally that the same target the symlink call is failing on works fine via a junction:
(No admin, no Developer Mode.)
Suggested fix
In the plugin-skill registration helper, on
process.platform === "win32"and only for directory targets, fall back to a junction whenfs.symlinkthrowsEPERM:Node's own
fs.symlink(target, path, "junction")on Windows already maps to a directory junction without requiring elevation, so depending on what's currently being passed, switching thetypeargument from default/"dir"to"junction"may be the entire fix. Worth confirming the call site is using the default/"dir"mode (which uses NT symlinks and triggers the EPERM).If a junction is genuinely undesirable (e.g. the skill target is a file, not a directory), fall back to a hard copy — slower but guaranteed to work — and surface a one-line warning telling the user how to enable Developer Mode if they want symlink behavior back.
Impact
Three "stock" plugin-shipped skills are silently unavailable on stock Windows installs:
browser-automation— blocks the entire@openclaw/browser-pluginskill surfaceobsidian-vault-maintainerwiki-maintainerI ship a desktop frontend on top of OpenClaw (Crystal: https://github.com/jvpflum/Crystal) and was tracking down "why does my browser skill list show empty" when I found this in
openclaw doctor. The error is logged but never bubbles up to the skills surface, so users hit it as an "I installed the plugin but the skill isn't there" mystery.Out of scope but related
The
EPERMerror itself is also printed to stderr on every CLI invocation that touches skill registration, including--jsonreads. That's the same anti-pattern flagged in #77942 (config-validator output leaking into machine-readable command stderr). Mentioning here for context but not asking for it to be fixed in this issue.Happy to send a focused PR + test once you confirm the preferred call-site change (switch
fs.symlinktype to"junction"vs add explicit fallback logic).