openclaw - ✅(Solved) Fix [Bug]: plugin-skills readlinkSync EINVAL on non-symlink directories [4 pull requests, 1 comments, 2 participants]

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…
GitHub stats
openclaw/openclaw#81432Fetched 2026-05-14 03:32:19
View on GitHub
Comments
1
Participants
2
Timeline
5
Reactions
1
Timeline (top)
cross-referenced ×4commented ×1

Error Message

log.warn(failed to inspect plugin skill symlink "${linkPath}": ${String(err)}); log.warn(failed to inspect plugin skill symlink "${linkPath}": ${String(err)});

Fix Action

Fixed

PR fix notes

PR #81446: fix(skills): replace generated plugin skill directories

Description (problem / solution / changelog)

Summary

  • inspect existing managed plugin-skill entries with lstat before readlink
  • replace Windows generated directory entries before creating the current skill link
  • keep non-generated entries untouched and add focused regression coverage

Fixes #81432.

Verification

  • pnpm vitest run src/agents/skills/plugin-skills.test.ts
  • git diff --check

Real behavior proof

Behavior addressed: Before this patch, publishPluginSkills() called fs.readlinkSync(linkPath) directly while publishing an active plugin skill. If linkPath already existed as a regular directory, Node raised EINVAL, the code logged failed to inspect plugin skill symlink, skipped the current skill, and left the stale directory in place. After this patch, publishPluginSkills() first runs fs.lstatSync(linkPath), removes generated Windows directory entries through the existing cleanup path, and creates the current plugin skill link.

Real setup tested: Local OpenClaw checkout at /home/chenglunhu/code/openclaw, branch fix/plugin-skills-nonsymlink-managed-entry-81432, commit 6f74f42e6eb1e24a36e00a9cdad361e6bccc76bd, using the real src/agents/skills/plugin-skills.ts implementation through pnpm exec tsx.

Exact steps or command run after the patch: Ran a pnpm exec tsx -e ... console script that created a temporary plugin skill with SKILL.md, created the managed plugin-skill entry with the same basename as a regular directory containing stale.txt, simulated Windows platform handling for publishPluginSkills([skill], { pluginSkillsDir: managed }), then printed the resulting link target and stale-file state.

Evidence after fix: Terminal output from the live command:

{
  "before": "regular-directory",
  "afterLinkTarget": "/tmp/oc-real-proof-H4C1fW/plugins/my-skill",
  "expected": "/tmp/oc-real-proof-H4C1fW/plugins/my-skill",
  "staleFileExists": false
}

Observed result after fix: The managed path that started as a regular directory became a link whose target exactly matched the active plugin skill directory, and the stale file under the old directory no longer existed. This confirms the EINVAL skip path is avoided for generated Windows directory entries.

Not tested: I did not run a manual Windows desktop openclaw skills list session.

Changed files

  • src/agents/skills/plugin-skills.test.ts (modified, +16/-0)
  • src/agents/skills/plugin-skills.ts (modified, +14/-4)

PR #81502: fix(skills): skip non-symlink plugin skill entries

Description (problem / solution / changelog)

Summary

  • avoid calling readlinkSync on existing regular directories in the managed plugin skills directory
  • leave non-symlink entries in place instead of treating them as generated symlinks
  • add coverage for the regular-directory collision case

Verification

  • pnpm test src/agents/skills/plugin-skills.test.ts
  • pnpm check:changed (fails on existing TypeScript errors in src/plugins/registry.runtime-config.test.ts, outside this change)

Fixes #81432

Changed files

  • src/agents/skills/plugin-skills.test.ts (modified, +15/-0)
  • src/agents/skills/plugin-skills.ts (modified, +8/-3)

PR #81571: fix(skills): check isSymbolicLink before readlinkSync to avoid EINVAL

Description (problem / solution / changelog)

Summary

Fixes #81432

On Windows (and other environments where symlinks may be replaced by real directories or junctions), publishPluginSkills calls fs.readlinkSync() without first checking if the path is a symbolic link. When the path is a regular directory, this throws EINVAL.

Root Cause

In src/agents/skills/plugin-skills.ts line 223, fs.readlinkSync(linkPath) is called unconditionally. On Windows, plugin skills directories may be real directories instead of symlinks (due to permissions or junction handling), causing EINVAL.

Fix

Use fs.lstatSync() to check if the path is a symbolic link before calling readlinkSync():

  • If it is a symlink → existing behavior (compare target, remove if stale)
  • If it is not a symlink (real directory) → remove so it can be recreated as a proper symlink
  • If path does not exist → fall through to symlink creation

This matches the approach suggested in the issue.

Testing

  • Code change reviewed line by line
  • Logic verified: symlink case preserves existing behavior, non-symlink case gracefully removes and recreates
  • Cannot reproduce on macOS (symlinks work normally), but the fix is logically sound for Windows environments
  • The existing test suite should continue to pass as the symlink behavior is unchanged

AI-assisted

This PR was written with AI assistance. I have reviewed and understand every line of the change.

Changed files

  • src/agents/skills/plugin-skills.ts (modified, +13/-4)

PR #81588: fix(skills): guard publishPluginSkills against EINVAL on non-symlink entries

Description (problem / solution / changelog)

Fixes #81432

Problem

publishPluginSkills in src/agents/skills/plugin-skills.ts calls fs.readlinkSync(linkPath) directly on every existing entry in ~/.openclaw/plugin-skills/ to compare against the desired target. When the entry is a real directory rather than a symlink — for example on Windows after a junction was replaced with a regular directory, or after a manual edit — readlinkSync throws EINVAL.

The existing catch path only treats ENOENT/ENOTDIR as benign, so EINVAL falls into the log.warn("failed to inspect plugin skill symlink …") branch and then continues, which means:

  1. A noisy warning is logged on every openclaw skills list / startup.
  2. The stale directory is never replaced with a proper symlink/junction.

Fix

Use fs.lstatSync first and only call readlinkSync when the entry is actually a symbolic link. Real directories fall through to removeGeneratedPluginSkillEntry, which is already fs.rmSync({ recursive: true, force: true }) and handles both symlinks and dirs. Behavior for symlinks is unchanged.

Test

Added a regression test in src/agents/skills/plugin-skills.test.ts that:

  • Pre-creates a real directory (with a sentinel file inside) at the publish path.
  • Invokes publishPluginSkills and asserts the sentinel is gone, the entry is now a symlink pointing at the target (or a directory on Windows since junctions appear as directories), and no EINVAL warning was emitted.

Verified the test fails on main and passes with this change:

$ node scripts/run-vitest.mjs run \
    --config test/vitest/vitest.agents-support.config.ts \
    src/agents/skills/plugin-skills.test.ts
 ✓ agents-support  src/agents/skills/plugin-skills.test.ts (24 tests) 35ms
 Test Files  1 passed (1)
      Tests  24 passed (24)

Changed files

  • src/agents/skills/plugin-skills.test.ts (modified, +44/-0)
  • src/agents/skills/plugin-skills.ts (modified, +11/-3)

Code Example

try {
    if (fs.readlinkSync(linkPath) === target) continue;
    fs.unlinkSync(linkPath);
} catch (err) {
    if (!isNotFoundError(err)) {
        log.warn(`failed to inspect plugin skill symlink "${linkPath}": ${String(err)}`);
        continue;
    }
}

---

try {
    const stat = fs.lstatSync(linkPath);
    if (stat.isSymbolicLink() && fs.readlinkSync(linkPath) === target) continue;
    if (stat.isSymbolicLink()) fs.unlinkSync(linkPath);
} catch (err) {
    if (!isNotFoundError(err)) {
        log.warn(`failed to inspect plugin skill symlink "${linkPath}": ${String(err)}`);
        continue;
    }
}
RAW_BUFFERClick to expand / collapse

Bug Report: plugin-skills readlinkSync EINVAL on non-symlink directories

Bug Description

The publishPluginSkills function in plugin-skills-79cwWJx9.js calls fs.readlinkSync(linkPath) without first checking if the path is a symbolic link. When the path is a regular directory, this throws EINVAL.

Location

dist/plugin-skills-79cwWJx9.js, line 149

Current Code

try {
    if (fs.readlinkSync(linkPath) === target) continue;
    fs.unlinkSync(linkPath);
} catch (err) {
    if (!isNotFoundError(err)) {
        log.warn(`failed to inspect plugin skill symlink "${linkPath}": ${String(err)}`);
        continue;
    }
}

Proposed Fix

try {
    const stat = fs.lstatSync(linkPath);
    if (stat.isSymbolicLink() && fs.readlinkSync(linkPath) === target) continue;
    if (stat.isSymbolicLink()) fs.unlinkSync(linkPath);
} catch (err) {
    if (!isNotFoundError(err)) {
        log.warn(`failed to inspect plugin skill symlink "${linkPath}": ${String(err)}`);
        continue;
    }
}

Steps to Reproduce

  1. Convert plugin-skills symlinks to real directories (e.g. due to Windows permissions)
  2. Run openclaw skills list
  3. See "failed to inspect plugin skill symlink" warnings with EINVAL

Environment

  • OS: Windows 10 (10.0.26200)
  • OpenClaw: 2026.5.7
  • Node: v24.15.0

Submit to

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 [Bug]: plugin-skills readlinkSync EINVAL on non-symlink directories [4 pull requests, 1 comments, 2 participants]