openclaw - ✅(Solved) Fix [Bug]: plugins enable <nonexistent-id> writes a stale plugin config entry and exits 0 [5 pull requests, 1 comments, 2 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#73551Fetched 2026-04-29 06:18:22
View on GitHub
Comments
1
Participants
2
Timeline
9
Reactions
0
Author
Timeline (top)
cross-referenced ×6labeled ×2commented ×1

openclaw plugins enable <id> does not validate that <id> is a discovered plugin before mutating config. A nonexistent plugin id is written to plugins.entries.<id> with enabled: true, then the CLI prints a success message and exits 0.

Error Message

  • Print a clear error like Plugin not found: <id>. Run `openclaw plugins list\ to see installed plugins.` (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)
  • 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.

Root Cause

  • 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.

Fix Action

Fixed

PR fix notes

PR #73554: fix(cli): reject missing plugin ids before config writes

Description (problem / solution / changelog)

Summary

  • Problem: openclaw plugins enable <id> and plugins disable <id> accepted nonexistent plugin ids and could write stale plugins.entries.<id> config.
  • Why it matters: A typo could leave persistent junk config and repeated stale-plugin warnings until manual cleanup.
  • What changed: Resolve the requested plugin id against the discovered plugin registry before mutating config; missing ids now exit 1 without writing config.
  • What did NOT change (scope boundary): This does not change plugin discovery, install/uninstall behavior, Gateway plugin loading, or cleanup of stale entries already written by older versions.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #73551
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: The plugin policy mutation commands used the raw CLI argument directly for config writes before verifying that the id matched a discovered plugin.
  • Missing detection / guardrail: Existing policy tests covered successful enable/disable paths, but not nonexistent plugin ids or no-write behavior on failure.
  • Contributing context (if known): The stale-plugin warning path can identify missing config entries, but the enable/disable write path did not use registry knowledge as a gate.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/cli/plugins-cli.policy.test.ts
  • Scenario the test should lock in: plugins enable missing-plugin and plugins disable missing-plugin exit 1 and do not call config write or registry refresh seams.
  • Why this is the smallest reliable guardrail: The bug is in the CLI policy mutation handler, so a focused command test can assert the write seam is not reached.
  • Existing test that already covers this (if any): None.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

openclaw plugins enable <missing-id> and openclaw plugins disable <missing-id> now fail with a clear plugin-not-found error and do not mutate openclaw.json.

Diagram (if applicable)

Before:
plugins enable fake-id -> write plugins.entries.fake-id -> stale warning later

After:
plugins enable fake-id -> registry lookup fails -> exit 1, no config write

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)
  • If any Yes, explain risk + mitigation: N/A

Repro + Verification

Environment

  • OS: Ubuntu/Linux
  • Runtime/container: Node 22, pnpm dev checkout
  • Model/provider: N/A
  • Integration/channel (if any): Plugins CLI
  • Relevant config (redacted): plugins.entries policy config

Steps

  1. Run openclaw plugins enable totally-fake-plugin-xyz.
  2. Inspect openclaw.json.
  3. Run openclaw plugins disable totally-fake-plugin-xyz.

Expected

  • Missing plugin ids fail before config mutation.
  • No stale plugins.entries.<id> entry is written.

Actual

  • Before this change, the enable path could write plugins.entries.<id> = { enabled: true } and exit 0.
  • After this change, missing ids exit 1 and the config write seam is not called.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Local verification:

  • pnpm test src/cli/plugins-cli.policy.test.ts -- --reporter=verbose
  • pnpm test src/cli/plugins-cli.policy.test.ts src/cli/plugins-cli.list.test.ts src/cli/plugins-cli.uninstall.test.ts src/cli/plugins-cli.install.test.ts src/cli/plugins-config.test.ts src/plugins/enable.test.ts -- --reporter=verbose
  • pnpm check:changed -- --base upstream/main
  • git diff --check

Human Verification (required)

  • Verified scenarios: Missing plugin ids for plugins enable and plugins disable reject before config writes; adjacent plugin CLI tests still pass.
  • Edge cases checked: Existing successful enable/disable policy paths still pass with discovered plugin ids. The registry snapshot includes disabled plugins, so valid disabled plugins remain enableable.
  • What you did not verify: Full packaged CLI invocation against a real user config.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes)
  • Config/env changes? (No)
  • Migration needed? (No)
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: Existing stale plugin entries written by older behavior are not automatically removed by this PR.
    • Mitigation: This PR prevents new stale entries through enable/disable; cleanup of already-stale config remains a separate doctor/fix concern.

Changed files

  • src/cli/plugins-cli-test-helpers.ts (modified, +29/-0)
  • src/cli/plugins-cli.policy.test.ts (modified, +135/-0)
  • src/cli/plugins-cli.ts (modified, +37/-6)

PR #73566: fix(cli): handle closed plugin uninstall prompt

Description (problem / solution / changelog)

Summary

  • Problem: openclaw plugins uninstall <id> could leave the confirmation prompt await unsettled when stdin closed before an answer, causing Node to exit 13 with Detected unsettled top-level await.
  • Why it matters: CI/scripts/detached shells get an internal Node crash instead of a clear CLI error; operators do not learn to use --force for non-interactive uninstall.
  • What changed: promptYesNo() now detects readline close/EOF before an answer and throws a typed prompt-closed error; plugins uninstall catches that case and exits 1 with an actionable message.
  • What did NOT change (scope boundary): Piped y / n answers and plugins uninstall --force remain supported; this does not change install records, uninstall planning, or plugin deletion semantics.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #73562
  • Related #73551
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: promptYesNo() awaited readline.question() without handling readline close / EOF, so closed stdin could leave the top-level CLI await unsettled.
  • Missing detection / guardrail: Existing prompt and uninstall tests covered normal answers and --force, but not EOF-before-answer.
  • Contributing context (if known): plugins uninstall already supports --force, but the interactive path did not fail cleanly when no confirmation input was possible.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/cli/prompt.test.ts, src/cli/plugins-cli.uninstall.test.ts
  • Scenario the test should lock in: readline close before an answer rejects with PromptInputClosedError; plugins uninstall converts that specific prompt failure into exit 1 without config/index/file mutation.
  • Why this is the smallest reliable guardrail: The bug is in the prompt helper and uninstall command handler; focused tests cover both the shared primitive and command-level UX.
  • Existing test that already covers this (if any): None.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

openclaw plugins uninstall <id> < /dev/null now fails with a clear CLI error instead of surfacing Node's unsettled top-level-await warning. Users can rerun interactively, pipe y/n, or pass --force for non-interactive uninstall.

Diagram (if applicable)

Before:
closed stdin -> readline question never answers -> unsettled top-level await -> Node exit 13

After:
closed stdin -> prompt close detected -> plugins uninstall exits 1 with --force guidance

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)
  • If any Yes, explain risk + mitigation: N/A

Repro + Verification

Environment

  • OS: Ubuntu/Linux
  • Runtime/container: Node 22.22.1, pnpm dev checkout
  • Model/provider: N/A
  • Integration/channel (if any): Plugins CLI
  • Relevant config (redacted): A managed plugin entry or install record for the uninstall target

Steps

  1. Ensure plugins uninstall <id> targets a managed plugin entry or install record.
  2. Run openclaw plugins uninstall <id> < /dev/null without --force.
  3. Observe the command result.

Expected

  • CLI exits 1 with an actionable confirmation-input error.
  • No config, install index, or plugin file mutation occurs.

Actual

  • Before this change, the prompt path could surface Node's Detected unsettled top-level await and exit 13.
  • After this change, the closed-input case is detected and handled before mutation.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Local verification:

  • pnpm test src/cli/prompt.test.ts src/cli/plugins-cli.uninstall.test.ts -- --reporter=verbose
  • pnpm check:changed -- --base upstream/main
  • git diff --check HEAD
  • Direct closed-stdin smoke against patched promptYesNo(): returns PromptInputClosedError with process exit 0 in the harness instead of Node exit 13.

Human Verification (required)

  • Verified scenarios: Prompt close before answer; uninstall prompt-close command path; existing --force uninstall path; normal prompt default/y/n behavior.
  • Edge cases checked: The fix avoids a blanket non-TTY ban, so piped answers remain supported by design.
  • What you did not verify: Full packaged pnpm openclaw plugins uninstall <id> < /dev/null against a real installed plugin, because the focused prompt and CLI seams directly cover the root cause.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes)
  • Config/env changes? (No)
  • Migration needed? (No)
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: promptYesNo() is shared by a few CLI flows, so changing EOF handling can affect other confirmation prompts.
    • Mitigation: The new behavior only triggers when readline closes before an answer; normal answers, defaults, and global --yes remain covered by existing prompt tests.

Changed files

  • src/cli/plugins-cli-test-helpers.ts (modified, +7/-0)
  • src/cli/plugins-cli.ts (modified, +20/-2)
  • src/cli/plugins-cli.uninstall.test.ts (modified, +52/-0)
  • src/cli/prompt.test.ts (modified, +35/-3)
  • src/cli/prompt.ts (modified, +35/-2)

PR #73623: fix: validate plugin id before writing config in plugins enable/disable

Description (problem / solution / changelog)

Summary

Validate that a plugin id exists in the discovered plugin registry before writing to plugins.entries config. For nonexistent ids, exit non-zero with a clear error message and do not mutate config. Fixes both plugins enable and plugins disable.

Changes

  • plugins enable: add pre-write validation gate — reject unknown ids with exit 1
  • plugins disable: add same pre-write validation gate for symmetry
  • Error message: "Plugin not found: <id>. Run openclaw plugins list to see installed plugins."

Testing

  • Unit test: plugins enable with nonexistent id asserts exit code != 0, error message contains plugin name, config not written
  • Unit test: plugins disable with nonexistent id asserts same behavior
  • Existing plugin enable/disable still works (integration smoke test)

Fixes openclaw/openclaw#73551

Changed files

  • src/cli/plugins-cli.enable-disable.test.ts (added, +75/-0)
  • src/cli/plugins-cli.ts (modified, +28/-0)

PR #73932: fix(cli): validate plugin ID before enabling

Description (problem / solution / changelog)

Summary

Fixes #73551

The plugins enable command now validates the plugin ID against discovered plugins before modifying config.

Problem

Previously, openclaw plugins enable <invalid-id> would silently write invalid plugin entry to config, causing potential gateway startup failures.

Solution

Added validation using buildPluginDiagnosticsReport to check if plugin ID exists before enabling.

Changes

+ const report = buildPluginDiagnosticsReport({ config: cfg });
+ const availableIds = report.plugins.map((p) => p.id);
+ if (!availableIds.includes(id)) {
+   defaultRuntime.log(theme.error(`Plugin "${id}" not found.`));
+   process.exitCode = 1;
+   return;
+ }

Test Plan

  1. openclaw plugins enable nonexistent-plugin → Error + exit 1
  2. openclaw plugins list shows available plugins
  3. openclaw plugins enable telegram → Success

Security

No security implications - purely CLI input validation.

Changed files

  • src/cli/plugins-cli.ts (modified, +24/-6)

PR #74016: fix: reject unknown plugin ids in plugins enable/disable

Description (problem / solution / changelog)

Background

openclaw plugins enable <id> and openclaw plugins disable <id> currently mutate plugins.entries.<id> even when the requested plugin was never discovered. That leaves a stale config entry behind, prints a contradictory success message, and exits 0 on typos.

Changes

  • validate the requested plugin id against the discovered plugin snapshot before mutating config
  • reuse the resolved canonical plugin id for enable/disable logging and slot selection
  • return a clear Plugin not found: <id>. Run \openclaw plugins list` to see installed plugins.` error and exit 1 when the plugin is unknown
  • add CLI regression coverage that asserts unknown enable/disable requests do not write config or refresh the registry

Validation

  • pnpm test src/cli/plugins-cli.policy.test.ts
  • pnpm exec oxfmt --check --threads=1 src/cli/plugins-cli.ts src/cli/plugins-cli.policy.test.ts
  • OPENCLAW_STATE_DIR=$(mktemp -d) pnpm openclaw plugins enable totally-fake-plugin-xyz → exits 1 and leaves config unchanged

Impact

  • typoed or nonexistent plugin ids stop failing open
  • operators no longer get stale plugins.entries.* warnings carried into later plugin commands
  • shell scripts can reliably branch on a non-zero exit when a requested plugin id does not exist

Fixes #73551.

Changed files

  • src/cli/plugins-cli.policy.test.ts (modified, +45/-0)
  • src/cli/plugins-cli.ts (modified, +30/-6)

Code Example

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.

---

{
  "plugins": {
    "entries": {
      "totally-fake-plugin-xyz": {
        "enabled": true
      }
    }
  }
}

---



---

=== 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

---

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.

---

- 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).
RAW_BUFFERClick to expand / collapse

Bug type

Regression (worked before, now fails)

Beta release blocker

No

Summary

openclaw plugins enable <id> does not validate that <id> is a discovered plugin before mutating config. A nonexistent plugin id is written to plugins.entries.<id> with enabled: true, then the CLI prints a success message and exits 0.

Steps to reproduce

  1. Use OpenClaw 2026.4.26 from a source checkout.
  2. Run pnpm openclaw plugins enable totally-fake-plugin-xyz.
  3. Observe a warning that the plugin was not found.
  4. Observe the CLI still prints Enabled plugin "totally-fake-plugin-xyz". Restart the gateway to apply.
  5. Inspect ~/.openclaw/openclaw.json.
  6. Observe plugins.entries.totally-fake-plugin-xyz = { "enabled": true }.

Expected behavior

plugins enable <id> 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: <id>. Run `openclaw plugins list\ to see installed plugins.`
  • Not modify openclaw.json.

plugins disable <id> 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).

extent analysis

TL;DR

The openclaw plugins enable command should validate the plugin ID against the discovered plugin registry before writing to the config, and exit with a non-zero status code if the plugin is not found.

Guidance

  • The issue is caused by the lack of validation of the plugin ID in the plugins enable command, which allows a nonexistent plugin ID to be written to the config.
  • To fix this, the plugins enable command should check if the plugin ID exists in the discovered plugin registry before writing to the config.
  • The plugins disable command should also be updated to follow the same validation rule to prevent creating or mutating stale entries for nonexistent plugins.
  • A unit test should be added to verify that the plugins enable command exits with a non-zero status code and prints an error message when a nonexistent plugin ID is provided.

Example

No code example is provided as the issue does not contain sufficient information about the codebase.

Notes

The fix should be applied to the plugins enable and plugins disable commands to ensure that only valid plugin IDs are written to the config. Additionally, the plugins uninstall command should be updated to handle non-interactive TTYs correctly.

Recommendation

Apply a workaround by manually editing the openclaw.json file to remove the stale config entry, and then update the plugins enable and plugins disable commands to validate the plugin ID against the discovered plugin registry.

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

plugins enable <id> 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: <id>. Run `openclaw plugins list\ to see installed plugins.`
  • Not modify openclaw.json.

plugins disable <id> should follow the same validation rule and should not create or mutate stale entries for nonexistent plugins.

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]: plugins enable <nonexistent-id> writes a stale plugin config entry and exits 0 [5 pull requests, 1 comments, 2 participants]