Summary
Two adjacent config-surface changes to give operators an escape hatch from the bundled runtime deps lazy-stage cycle, which has been a recurring source of event-loop stalls and disk bloat (#74949, #73532, #56733, #74860 are all downstream symptoms).
What's already in source
Reading dist/bundled-runtime-deps-*.js and dist/loader-*.js in 2026.4.24 shows two relevant code paths that exist but are gated:
loader-NucjcOgv.js:2240 references installBundledRuntimeDeps: false as an option — the code branch is there but isn't surfaced through openclaw.json config schema or CLI
bundled-runtime-deps-BdEAdjwi.js#resolveBundledRuntimeDepsExternalBaseDir already implements an isolatedExecutionRoot path (separate staging dir + atomic replace) — but the default installExecutionRoot is the legacy shared-root path
Operators currently have to either patch package.json manually (per #74949) or apply OPENCLAW_PLUGIN_STAGE_DIR via systemd drop-in to get the isolated path. Neither is documented.
Proposed changes
1. Surface installBundledRuntimeDeps: false as a config field
# openclaw.json
{
"plugins": {
"installBundledRuntimeDeps": false // default: true
}
}
When false, the plugin loader should:
- Skip the lazy-stage spawnSync entirely
- Assume the operator has provided the runtime deps externally (e.g., via the host's package.json +
npm install already done)
- Fail fast with a clear error if a required plugin dep is missing, rather than silently re-installing
This is the proper fix for operators who:
2. Make isolatedExecutionRoot the default
The current default puts plugin deps installs at the same root the host openclaw uses. For multi-plugin deployments this causes the destructive prune problem #74949 describes (different plugins' lazy-stage cycles mutually pruning each other's deps).
Changing the default to the isolated path that already exists in source would:
- Make a single-host, multi-plugin deployment safe out of the box
- Remove the need for users to discover and apply the
OPENCLAW_PLUGIN_STAGE_DIR env var as a workaround
- Not change behavior for anyone explicitly opting into the legacy shared root
3. Document the resulting matrix
installBundledRuntimeDeps |
isolatedExecutionRoot |
Behavior |
| true (default) |
true (proposed default) |
Per-plugin isolated lazy-stage. No mutual prune. Cycles still happen but safe. |
| true |
false |
Legacy 2026.4.24 default. Destructive prune across plugins. |
| false |
n/a |
Operator-managed deps. No spawnSync. Plugin loader trusts host node_modules. |
Why this matters
Giving operators these two knobs would let production deployments turn lazy-stage off entirely, while keeping the convenience for single-plugin / dev setups that benefit from the auto-install behavior.
Environment of the deployment that motivated this request
- OpenClaw: 2026.4.24
- Node: v22.22.0, npm: v10
- Plugins enabled: discord, feishu, telegram, qqbot, wecom, ddingtalk, openai, kimi (8 channels)
- Disk impact: ~38 GB cache bloat from repeated re-installs across ~14 days, 91% disk usage on a 120 GB volume before manual cleanup
- Channel impact: Discord WebSocket invalid-session loops during npm storms; Feishu HTTP timeouts (axios 30s)
Happy to test patches against this deployment.
Summary
Two adjacent config-surface changes to give operators an escape hatch from the bundled runtime deps lazy-stage cycle, which has been a recurring source of event-loop stalls and disk bloat (#74949, #73532, #56733, #74860 are all downstream symptoms).
What's already in source
Reading
dist/bundled-runtime-deps-*.jsanddist/loader-*.jsin 2026.4.24 shows two relevant code paths that exist but are gated:loader-NucjcOgv.js:2240referencesinstallBundledRuntimeDeps: falseas an option — the code branch is there but isn't surfaced throughopenclaw.jsonconfig schema or CLIbundled-runtime-deps-BdEAdjwi.js#resolveBundledRuntimeDepsExternalBaseDiralready implements anisolatedExecutionRootpath (separate staging dir + atomic replace) — but the defaultinstallExecutionRootis the legacy shared-root pathOperators currently have to either patch
package.jsonmanually (per #74949) or applyOPENCLAW_PLUGIN_STAGE_DIRvia systemd drop-in to get the isolated path. Neither is documented.Proposed changes
1. Surface
installBundledRuntimeDeps: falseas a config fieldWhen
false, the plugin loader should:npm installalready done)This is the proper fix for operators who:
package.json(the 🐛 [Bug] Gateway crashing on plugin initialization due to empty dependencies in isolated package.json (npm v11 / Node v25) #74949 workaround)2. Make
isolatedExecutionRootthe defaultThe current default puts plugin deps installs at the same root the host openclaw uses. For multi-plugin deployments this causes the destructive prune problem #74949 describes (different plugins' lazy-stage cycles mutually pruning each other's deps).
Changing the default to the isolated path that already exists in source would:
OPENCLAW_PLUGIN_STAGE_DIRenv var as a workaround3. Document the resulting matrix
installBundledRuntimeDepsisolatedExecutionRootWhy this matters
Giving operators these two knobs would let production deployments turn lazy-stage off entirely, while keeping the convenience for single-plugin / dev setups that benefit from the auto-install behavior.
Environment of the deployment that motivated this request
Happy to test patches against this deployment.