openclaw - ✅(Solved) Fix [Bug]: OpenClaw creates self-referential `openclaw-unknown-*` runtime dep cache that causes ENOTEMPTY crashes [1 pull requests, 2 comments, 3 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#72956Fetched 2026-04-28 06:29:32
View on GitHub
Comments
2
Participants
3
Timeline
9
Reactions
1
Timeline (top)
cross-referenced ×5commented ×2closed ×1labeled ×1

After a clean openclaw gateway start, the ~/.openclaw/plugin-runtime-deps/ directory contains two cache directories:

openclaw-2026.4.24-da6bdffc3d96   ← correct, versioned
openclaw-unknown-5504045e5896     ← zombie, self-referential

The unknown-* cache is created every time the gateway boots, even from a completely empty plugin-runtime-deps/. On subsequent restarts (or when bundled channels load in parallel), this causes ENOTEMPTY errors:

[channels] failed to load bundled channel qqbot: ENOTEMPTY,
  Directory not empty:
  ~/.openclaw/plugin-runtime-deps/openclaw-unknown-5504045e5896/dist/extensions/node_modules/openclaw/plugin-sdk

This cascades into unhandled rejections and crash loops.


Root Cause

In dist/bundled-runtime-deps-*.js, resolveExternalBundledRuntimeDepsInstallRoot():

function resolveExternalBundledRuntimeDepsInstallRoot(params) {
    const packageRoot = resolveBundledPluginPackageRoot(params.pluginRoot) ?? params.pluginRoot;
    const packageKey = `openclaw-${sanitizePathSegment(readPackageVersion(packageRoot))}-${createPathHash(packageRoot)}`;
    return path.join(resolveBundledRuntimeDepsExternalBaseDir(params.env), packageKey);
}

resolveBundledPluginPackageRoot() walks from pluginRootpath.dirname() twice, expecting to find dist/extensions/<plugin>dist → package root. However, when a bundled plugin's extension directory is already inside the plugin-runtime-deps cache (e.g. during a second plugin load pass, or when bundled channels reference plugin SDK), it resolves the cache directory itself as packageRoot.

The cache directory has no package.json, so readPackageVersion() returns "unknown". And createPathHash() of that cache dir happens to be 5504045e5896.

OpenClaw then creates a self-referential symlink farm inside unknown-5504045e5896/dist/, pointing back to the real da6bdffc3d96/dist/. This creates two caches with identical contents but different names, racing each other on replaceNodeModulesDir()'s fs.renameSync().

PR fix notes

PR #73205: fix(bundled-runtime-deps): collapse nested cache pluginRoot to enclosing key (Fixes #72956)

Description (problem / solution / changelog)

Summary

Eliminates the openclaw-unknown-<hash> zombie cache that appeared next to the legitimate versioned openclaw-<version>-<hash> directory under ~/.openclaw/plugin-runtime-deps/, and the ENOTEMPTY crash loops that followed when bundled channels raced replaceNodeModulesDir().

Root cause

resolveBundledPluginPackageRoot() walks pluginRoot → ../.. → packageRoot expecting dist/extensions/<plugin>. When the gateway later resolves a plugin whose pluginRoot already lives inside the cache (e.g. plugin-sdk loaded transitively at <cache>/openclaw-2026.4.24-.../dist/extensions/node_modules/openclaw/plugin-sdk), that walk does not match and the function returns null. The caller then falls back to the raw pluginRoot.

resolveExistingExternalBundledRuntimeDepsRoots() rejected that fallback because the relative path from the stage base contained directory separators, so the resolver minted a brand new openclaw-unknown-<pathhash> directory key. The two caches then duplicated each other and clashed on rename.

Fix

When pluginRoot resolves to any descendant of <stageBase>/openclaw-*, treat the first segment (the existing versioned cache key) as the install root instead of inventing a new one. The new path is unit-tested as a regression guard for #72956.

Test plan

  • New unit test resolves nested cache pluginRoot to enclosing versioned cache (regression for #72956) in bundled-runtime-deps.test.ts.
  • Existing mirroredPluginRoot test still passes (single-segment cache key path remains valid).

Fixes #72956

Changed files

  • src/plugins/bundled-runtime-deps.test.ts (modified, +36/-0)
  • src/plugins/bundled-runtime-deps.ts (modified, +12/-10)

Code Example

openclaw-2026.4.24-da6bdffc3d96   ← correct, versioned
openclaw-unknown-5504045e5896     ← zombie, self-referential

---

[channels] failed to load bundled channel qqbot: ENOTEMPTY,
  Directory not empty:
  ~/.openclaw/plugin-runtime-deps/openclaw-unknown-5504045e5896/dist/extensions/node_modules/openclaw/plugin-sdk

---

function resolveExternalBundledRuntimeDepsInstallRoot(params) {
    const packageRoot = resolveBundledPluginPackageRoot(params.pluginRoot) ?? params.pluginRoot;
    const packageKey = `openclaw-${sanitizePathSegment(readPackageVersion(packageRoot))}-${createPathHash(packageRoot)}`;
    return path.join(resolveBundledRuntimeDepsExternalBaseDir(params.env), packageKey);
}

---
RAW_BUFFERClick to expand / collapse

Bug type

Crash (process/app exits or hangs)

Beta release blocker

No

Summary

After a clean openclaw gateway start, the ~/.openclaw/plugin-runtime-deps/ directory contains two cache directories:

openclaw-2026.4.24-da6bdffc3d96   ← correct, versioned
openclaw-unknown-5504045e5896     ← zombie, self-referential

The unknown-* cache is created every time the gateway boots, even from a completely empty plugin-runtime-deps/. On subsequent restarts (or when bundled channels load in parallel), this causes ENOTEMPTY errors:

[channels] failed to load bundled channel qqbot: ENOTEMPTY,
  Directory not empty:
  ~/.openclaw/plugin-runtime-deps/openclaw-unknown-5504045e5896/dist/extensions/node_modules/openclaw/plugin-sdk

This cascades into unhandled rejections and crash loops.


Root Cause Analysis

In dist/bundled-runtime-deps-*.js, resolveExternalBundledRuntimeDepsInstallRoot():

function resolveExternalBundledRuntimeDepsInstallRoot(params) {
    const packageRoot = resolveBundledPluginPackageRoot(params.pluginRoot) ?? params.pluginRoot;
    const packageKey = `openclaw-${sanitizePathSegment(readPackageVersion(packageRoot))}-${createPathHash(packageRoot)}`;
    return path.join(resolveBundledRuntimeDepsExternalBaseDir(params.env), packageKey);
}

resolveBundledPluginPackageRoot() walks from pluginRootpath.dirname() twice, expecting to find dist/extensions/<plugin>dist → package root. However, when a bundled plugin's extension directory is already inside the plugin-runtime-deps cache (e.g. during a second plugin load pass, or when bundled channels reference plugin SDK), it resolves the cache directory itself as packageRoot.

The cache directory has no package.json, so readPackageVersion() returns "unknown". And createPathHash() of that cache dir happens to be 5504045e5896.

OpenClaw then creates a self-referential symlink farm inside unknown-5504045e5896/dist/, pointing back to the real da6bdffc3d96/dist/. This creates two caches with identical contents but different names, racing each other on replaceNodeModulesDir()'s fs.renameSync().

Steps to reproduce

  1. npm install -g openclaw (version 2026.4.24)
  2. rm -rf ~/.openclaw/plugin-runtime-deps
  3. openclaw gateway
  4. After boot completes, observe ~/.openclaw/plugin-runtime-deps/

Expected behavior

One directory: openclaw-2026.4.24-da6bdffc3d96

Actual behavior

Two directories, including openclaw-unknown-5504045e5896

OpenClaw version

2026.4.24

Operating system

macOS (darwin/arm64), Node.js 25.8.1

Install method

npm global

Model

ollama/kimi-k2.5

Provider / routing chain

openclaw -> ollama cloud -> kimi-k2.5

Additional provider/model setup details

No response

Logs, screenshots, and evidence

Impact and severity

No response

Additional information

No response

extent analysis

TL;DR

The issue can be fixed by modifying the resolveExternalBundledRuntimeDepsInstallRoot function to correctly handle cases where the bundled plugin's extension directory is already inside the plugin-runtime-deps cache.

Guidance

  • Review the resolveBundledPluginPackageRoot function to ensure it correctly resolves the package root, even when the extension directory is inside the cache.
  • Consider adding a check to prevent resolveExternalBundledRuntimeDepsInstallRoot from creating a self-referential symlink farm.
  • Verify that the readPackageVersion function returns the correct version, and not "unknown", when the package root is correctly resolved.
  • Test the changes by running the steps to reproduce and observing the resulting plugin-runtime-deps directory.

Example

function resolveExternalBundledRuntimeDepsInstallRoot(params) {
    const packageRoot = resolveBundledPluginPackageRoot(params.pluginRoot) ?? params.pluginRoot;
    // Add a check to prevent self-referential symlink farm
    if (packageRoot.includes('plugin-runtime-deps')) {
        // Handle this case correctly
    }
    const packageKey = `openclaw-${sanitizePathSegment(readPackageVersion(packageRoot))}-${createPathHash(packageRoot)}`;
    return path.join(resolveBundledRuntimeDepsExternalBaseDir(params.env), packageKey);
}

Notes

The provided code snippet and steps to reproduce suggest that the issue is related to the resolveExternalBundledRuntimeDepsInstallRoot function. However, without further information or context, it is difficult to provide a complete solution.

Recommendation

Apply a workaround by modifying the resolveExternalBundledRuntimeDepsInstallRoot function to correctly handle cases where the bundled plugin's extension directory is already inside the plugin-runtime-deps cache. This will prevent the creation of self-referential symlinks and

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…

FAQ

Expected behavior

One directory: openclaw-2026.4.24-da6bdffc3d96

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]: OpenClaw creates self-referential `openclaw-unknown-*` runtime dep cache that causes ENOTEMPTY crashes [1 pull requests, 2 comments, 3 participants]