Environment
- OpenClaw 2026.4.20 (also reproduced on 2026.4.15)
- Installed via
npm i -g openclaw@latest to /usr/lib/node_modules/openclaw/
- Running under systemd with hardened unit:
ProtectSystem=strict
ProtectHome=read-only
NoNewPrivileges=yes
ReadWritePaths=/mnt/data/.openclaw /home/openclaw/.openclaw /tmp … (narrow allowlist; /usr/lib/ excluded)
Symptom
After openclaw update --yes --no-restart --json:
{
"status": "error",
"reason": "openclaw doctor",
"steps": [
{ "name": "global update", "exitCode": 0, "stdoutTail": "changed 454 packages in 36s" },
{ "name": "openclaw doctor", "exitCode": 1,
"stderrTail": "Error: Cannot find module '@slack/web-api'\nRequire stack:\n- /usr/lib/node_modules/openclaw/dist/extensions/slack/client-*.js" }
]
}
No node_modules/ directory exists in any /usr/lib/node_modules/openclaw/dist/extensions/*/. All 15 bundled plugins that declare "openclaw.bundle.stageRuntimeDependencies": true (slack, discord, telegram, whatsapp, feishu, matrix, qqbot, nostr, webhooks, codex, google, diffs, acpx, amazon-bedrock, amazon-bedrock-mantle) have their deps missing.
Root cause
The 2026.4.20 plugin-repair path (src/plugins/loader.ts, per release notes: "install bundled runtime dependencies into each plugin's own runtime directory") attempts to write into /usr/lib/node_modules/openclaw/dist/extensions/<id>/node_modules/. Under ProtectSystem=strict that path is mounted read-only inside the service's mount namespace, and NoNewPrivileges=yes blocks any sudo escalation. The staging call fails with EROFS and is silently swallowed; subsequent plugin imports then fail module resolution and openclaw doctor crashes.
openclaw doctor --fix doesn't perform staging either — it imports plugins eagerly and crashes on the same missing module before any repair logic runs. There is no documented openclaw plugins install-deps or equivalent CLI.
Related reports
Proposed fixes (in preference order)
- Env-overridable staging target. Accept
OPENCLAW_PLUGIN_STAGE_DIR (or $STATE_DIRECTORY from the systemd unit) so operators can point staging at a ReadWritePaths-included directory. Lets hardened hosts stage deps without loosening ProtectSystem=strict on the gateway unit.
- Explicit
openclaw plugins install-deps CLI that installs all bundled-plugin deps on demand, runnable from an out-of-namespace context (e.g., a systemd ExecStartPre= unit or update hook). Wrappers can call this instead of implementing custom staging logic.
- Document the incompatibility between
stageRuntimeDependencies and ProtectSystem=strict in docs/install/ so operators know either to loosen hardening or pre-stage deps before enabling.
Current workaround
Patched our update wrapper to run npm install --ignore-scripts --no-fund --no-audit as root in each bundled plugin dir that declares stageRuntimeDependencies: true and lacks node_modules/. Invoked in a recovery branch when openclaw update exits with "reason": "openclaw doctor" + Cannot find module '@, and unconditionally post-update (because npm i -g openclaw@latest wipes plugin node_modules/ on every run, even on same-version reinstalls).
Happy to share the wrapper patch if it helps.
Environment
npm i -g openclaw@latestto/usr/lib/node_modules/openclaw/ProtectSystem=strictProtectHome=read-onlyNoNewPrivileges=yesReadWritePaths=/mnt/data/.openclaw /home/openclaw/.openclaw /tmp …(narrow allowlist;/usr/lib/excluded)Symptom
After
openclaw update --yes --no-restart --json:{ "status": "error", "reason": "openclaw doctor", "steps": [ { "name": "global update", "exitCode": 0, "stdoutTail": "changed 454 packages in 36s" }, { "name": "openclaw doctor", "exitCode": 1, "stderrTail": "Error: Cannot find module '@slack/web-api'\nRequire stack:\n- /usr/lib/node_modules/openclaw/dist/extensions/slack/client-*.js" } ] }No
node_modules/directory exists in any/usr/lib/node_modules/openclaw/dist/extensions/*/. All 15 bundled plugins that declare"openclaw.bundle.stageRuntimeDependencies": true(slack, discord, telegram, whatsapp, feishu, matrix, qqbot, nostr, webhooks, codex, google, diffs, acpx, amazon-bedrock, amazon-bedrock-mantle) have their deps missing.Root cause
The 2026.4.20 plugin-repair path (
src/plugins/loader.ts, per release notes: "install bundled runtime dependencies into each plugin's own runtime directory") attempts to write into/usr/lib/node_modules/openclaw/dist/extensions/<id>/node_modules/. UnderProtectSystem=strictthat path is mounted read-only inside the service's mount namespace, andNoNewPrivileges=yesblocks any sudo escalation. The staging call fails with EROFS and is silently swallowed; subsequent plugin imports then fail module resolution andopenclaw doctorcrashes.openclaw doctor --fixdoesn't perform staging either — it imports plugins eagerly and crashes on the same missing module before any repair logic runs. There is no documentedopenclaw plugins install-depsor equivalent CLI.Related reports
Proposed fixes (in preference order)
OPENCLAW_PLUGIN_STAGE_DIR(or$STATE_DIRECTORYfrom the systemd unit) so operators can point staging at aReadWritePaths-included directory. Lets hardened hosts stage deps without looseningProtectSystem=stricton the gateway unit.openclaw plugins install-depsCLI that installs all bundled-plugin deps on demand, runnable from an out-of-namespace context (e.g., a systemdExecStartPre=unit or update hook). Wrappers can call this instead of implementing custom staging logic.stageRuntimeDependenciesandProtectSystem=strictindocs/install/so operators know either to loosen hardening or pre-stage deps before enabling.Current workaround
Patched our update wrapper to run
npm install --ignore-scripts --no-fund --no-auditas root in each bundled plugin dir that declaresstageRuntimeDependencies: trueand lacksnode_modules/. Invoked in a recovery branch whenopenclaw updateexits with"reason": "openclaw doctor"+Cannot find module '@, and unconditionally post-update (becausenpm i -g openclaw@latestwipes pluginnode_modules/on every run, even on same-version reinstalls).Happy to share the wrapper patch if it helps.