Skip to content

Bundled-plugin stageRuntimeDependencies fails under ProtectSystem=strict (EROFS; gateway can't write /usr/lib) #69923

@fawadafr

Description

@fawadafr

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)

  1. 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.
  2. 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.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions