Bug type
Regression (worked before, now fails)
Beta release blocker
No
Summary
openclaw plugins enable does not validate that is a discovered plugin before mutating config. A nonexistent plugin id is written to plugins.entries. with enabled: true, then the CLI prints a success message and exits 0.
Steps to reproduce
- Use OpenClaw 2026.4.26 from a source checkout.
- Run pnpm openclaw plugins enable totally-fake-plugin-xyz.
- Observe a warning that the plugin was not found.
- Observe the CLI still prints Enabled plugin "totally-fake-plugin-xyz". Restart the gateway to apply.
- Inspect ~/.openclaw/openclaw.json.
- Observe plugins.entries.totally-fake-plugin-xyz = { "enabled": true }.
Expected behavior
plugins enable should validate the id against the discovered plugin registry before writing config.
For a nonexistent id, it should:
- Exit non-zero.
- Print a clear error like Plugin not found: . Run `openclaw plugins list\ to see installed plugins.`
- Not modify openclaw.json.
plugins disable should follow the same validation rule and should not create or mutate stale entries for nonexistent plugins.
Actual behavior
The CLI emits a not-found warning, writes the stale config entry, prints a success message, and exits 0.
Observed result:
Config warnings:
- plugins.entries.totally-fake-plugin-xyz: plugin not found: totally-fake-plugin-xyz (stale config entry ignored; remove it from plugins config)
Enabled plugin "totally-fake-plugin-xyz". Restart the gateway to apply.
Persisted config contains:
{
"plugins": {
"entries": {
"totally-fake-plugin-xyz": {
"enabled": true
}
}
}
}
OpenClaw version
2026.4.26
Operating system
Ubuntu 24.04.4 LTS
Install method
pnpm dev
Model
N/A
Provider / routing chain
N/A
Additional provider/model setup details
No response
Logs, screenshots, and evidence
Logs, screenshots, and evidence
=== Step 3: enable a nonexistent plugin id (the bug) ===
$ pnpm openclaw plugins enable totally-fake-plugin-xyz
> node scripts/run-node.mjs plugins enable totally-fake-plugin-xyz
- plugins.entries.totally-fake-plugin-xyz: plugin not found: totally-fake-plugin-xyz (stale config entry ignored; remove it from plugins config)
Config overwrite: /home/orin/.openclaw/openclaw.json (sha256 99d6be97c9b54512f3e4d898cf18173e807c2435d8ef8d45762a5c3818f95080 -> a5e6a17f68de9a8a02a07268947cd70400217ea4907fdf6f6d44eca3217394ac, backup=/home/orin/.openclaw/openclaw.json.bak)
Config warnings:
- plugins.entries.totally-fake-plugin-xyz: plugin not found: totally-fake-plugin-xyz (stale config entry ignored; remove it from plugins config)
Enabled plugin "totally-fake-plugin-xyz". Restart the gateway to apply.
$ echo $?
0
=== Persisted in config ===
$ python3 -c "
import json
d = json.load(open('/home/orin/.openclaw/openclaw.json'))
print('totally-fake-plugin-xyz =', d['plugins']['entries'].get('totally-fake-plugin-xyz'))
print('total entries:', len(d['plugins']['entries']))
"
totally-fake-plugin-xyz = {'enabled': True}
total entries: 5
=== Subsequent plugin command inherits the warning ===
$ pnpm openclaw plugins disable whatsapp
- plugins.entries.totally-fake-plugin-xyz: plugin not found: totally-fake-plugin-xyz (stale config entry ignored; remove it from plugins config)
... (then proceeds to disable whatsapp; the junk warning rides every subsequent plugins-touching command)
=== Counter-example: plugins inspect on the same id correctly errors out, no config mutation ===
$ pnpm openclaw plugins inspect totally-fake-plugin
Plugin not found: totally-fake-plugin
ELIFECYCLE Command failed with exit code 1.
$ python3 -c "
import json
d = json.load(open('/home/orin/.openclaw/openclaw.json'))
print('totally-fake-plugin in entries?', 'totally-fake-plugin' in d['plugins']['entries'])
"
totally-fake-plugin in entries? False
=== Recovery via stock CLI is blocked ===
$ pnpm openclaw plugins disable totally-fake-plugin-xyz
... (prints same warning, flips enabled to false, keeps the entry, exit 0)
$ python3 -c "
import json
d = json.load(open('/home/orin/.openclaw/openclaw.json'))
print(d['plugins']['entries'].get('totally-fake-plugin-xyz'))
"
{'enabled': False}
$ pnpm openclaw plugins uninstall totally-fake-plugin-xyz
Plugin: totally-fake-plugin-xyz
Will remove: config entry
Uninstall plugin "totally-fake-plugin-xyz"? [y/N] Warning: Detected unsettled top-level await at file:///home/orin/Gittensor/Test/openclaw/openclaw.mjs:233
if (await tryImport("./dist/entry.js")) {
^
ELIFECYCLE Command failed with exit code 13.
(uninstall is interactive; in a non-TTY shell it crashes Node with exit 13 instead of failing cleanly with a "requires interactive TTY" error like other interactive subcommands)
=== Cleanup actually requires manual JSON editing ===
$ python3 -c "
import json
p = '/home/orin/.openclaw/openclaw.json'
d = json.load(open(p))
d['plugins']['entries'].pop('totally-fake-plugin-xyz', None)
json.dump(d, open(p,'w'), indent=2)
print('cleaned')
"
cleaned
Impact and severity
Affected users/systems/channels:
- Every operator who runs `openclaw plugins enable <id>` with a typo'd or otherwise nonexistent id. Linux directly observed (Ubuntu 24.04 / Node 22.22.2 / pnpm 10.33.0); the bug is in the CLI/config write path and platform-agnostic, so behavior is expected on macOS/Windows but only Linux was directly reproduced.
- Every plugin-touching command thereafter surfaces an inherited "stale config entry ignored; remove it from plugins config" warning until the operator manually edits openclaw.json.
Severity:
- Annoying with persistent state corruption. The config is silently mutated to include a non-functional entry; subsequent commands carry forward a warning the operator cannot dismiss without manual JSON editing.
- Not a security/data-loss bug per se — the entry is non-functional at load time (the gateway logs say "stale config entry ignored") — but it visibly degrades CLI output and trains operators to ignore warnings, which raises the cost of catching real plugins-config issues.
- Self-contradicting CLI output ("plugin not found" warning + "Enabled plugin <id>" success message in the same response) is a trust-eroding paper cut.
Frequency:
- Always, deterministic. 100% reproduction with any string that does not match an installed plugin id. Independent of model/provider/transport.
Consequence:
- Silent config corruption: ~/.openclaw/openclaw.json grows a junk entry on every fat-fingered enable.
- No clean recovery path through stock CLI: `plugins disable` keeps the entry; `plugins uninstall` requires an interactive TTY and crashes Node with exit 13 in non-TTY shells; the operator is forced to hand-edit the JSON.
- Misleading "success" exit code (0) breaks shell scripts and CI flows that branch on `openclaw plugins enable <id>` returning non-zero on a typo. Operators who automate plugin enablement against a list (e.g. provisioning scripts) will accept silent acceptance of stale ids.
- No grounded evidence of missed messages, failed onboarding, or extra cost.
Additional information
- Regression status: not classified as a Regression. Last-known-good not directly observed; no bisect performed.
- Likely fix locus: the action handler for `plugins enable` (commander definition under `src/cli/cli-plugins/` or wherever the parser lives in this build). The validator that emits the "plugin not found ... stale config entry ignored" warning already runs — promote it to a hard gate when the operator just supplied the id on the command line. The same gate should be added to `plugins disable` for symmetry, since today it also silently flips state on nonexistent ids.
- Suggested regression test: a unit test next to the `plugins enable` action that passes a definitely-not-registered id (e.g. seeded mock plugin registry containing only "real-plugin"; invoke enable with "fake-plugin"), and asserts (a) exit code is non-zero, (b) error message names the missing plugin and points at `plugins list`, (c) the config-write seam is not invoked. Pair with a parity test on `plugins disable` and (if the uninstall hang is fixed in the same PR) `plugins uninstall`.
- Related findings from the same session worth potentially separate issues:
- `plugins uninstall <id>` requires an interactive TTY (prompts `[y/N]`) but does not detect non-TTY stdin and instead crashes Node with `Detected unsettled top-level await ... exit code 13`. Other interactive subcommands (e.g. `models auth login`) emit `requires interactive TTY` and exit 1 cleanly. Could file separately.
- `plugins disable <nonexistent-id>` reports "Disabled plugin <id>. Restart the gateway to apply." with the same self-contradicting warning and keeps the junk entry. Same root cause as this issue (no validate-before-write), so a single PR can fix both surfaces.
- `config unset <absent-path>` returns "Config path not found: <path>" with exit 1, and is non-idempotent — repeated unsets of the same already-absent path produce identical errors. Convention-dependent (npm idempotent, git non-idempotent), so this one is more debatable. Not bundling.
- Dedupe checked against the openclaw issue corpus on 2026-04-28: no existing open or closed issue matches the "plugins enable persists nonexistent plugin id" behavior. Closest text match (#65319, open) is about contradictory `plugins.allow` guidance for the `/lossless` command — different code path (allow-list discovery, not enable-write), not a duplicate.
- Not exercised in this repro: per-agent plugin overrides (`plugins.entries` is global config, but per-agent state files exist under `~/.openclaw/agents/<id>/agent/`); whether `plugins enable` against a plugin in a third-party marketplace surfaces the same gap; concurrent enable invocations across multiple shells (the config-write path uses `Config overwrite: ... backup=...` semantics so a race is possible but not directly observed).
Bug type
Regression (worked before, now fails)
Beta release blocker
No
Summary
openclaw plugins enable does not validate that is a discovered plugin before mutating config. A nonexistent plugin id is written to plugins.entries. with enabled: true, then the CLI prints a success message and exits 0.
Steps to reproduce
Expected behavior
plugins enable should validate the id against the discovered plugin registry before writing config.
For a nonexistent id, it should:
plugins disable should follow the same validation rule and should not create or mutate stale entries for nonexistent plugins.
Actual behavior
The CLI emits a not-found warning, writes the stale config entry, prints a success message, and exits 0.
Observed result:
Persisted config contains:
{ "plugins": { "entries": { "totally-fake-plugin-xyz": { "enabled": true } } } }OpenClaw version
2026.4.26
Operating system
Ubuntu 24.04.4 LTS
Install method
pnpm dev
Model
N/A
Provider / routing chain
N/A
Additional provider/model setup details
No response
Logs, screenshots, and evidence
Logs, screenshots, and evidence
Impact and severity
Additional information