Skip to content

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

Merged
hxy91819 merged 3 commits into
openclaw:mainfrom
ai-hpc:fix/plugins-missing-id-policy
May 3, 2026
Merged

fix(cli): reject missing plugin ids before config writes#73554
hxy91819 merged 3 commits into
openclaw:mainfrom
ai-hpc:fix/plugins-missing-id-policy

Conversation

@ai-hpc

@ai-hpc ai-hpc commented Apr 28, 2026

Copy link
Copy Markdown
Member

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

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.

@openclaw-barnacle openclaw-barnacle Bot added cli CLI command changes size: S labels Apr 28, 2026
@greptile-apps

greptile-apps Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a bug where plugins enable <id> and plugins disable <id> would write stale config entries for nonexistent plugin IDs. The fix adds a registry lookup via buildPluginRegistrySnapshotReport before any config mutation, and the new tests cover both the rejection path and the id-over-name priority case.

Confidence Score: 5/5

Safe to merge — fix is well-scoped and tests cover the critical paths.

Only a P2 test symmetry gap found; production logic is correct and the id-over-name priority is properly tested.

No files require special attention.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/cli/plugins-cli.policy.test.ts
Line: 119-132

Comment:
**Missing `setPluginEnabledInConfig` assertion in disable rejection test**

The `enable` rejection test explicitly asserts `expect(enablePluginInConfig).not.toHaveBeenCalled()`, but the symmetric `disable` rejection test doesn't assert that `setPluginEnabledInConfig` was not called. Adding this assertion would close the gap and make both tests structurally equivalent.

```suggestion
    expect(runtimeErrors).toContain(
      "Plugin not found: missing-plugin. Run `openclaw plugins list` to see installed plugins.",
    );
    expect(setPluginEnabledInConfig).not.toHaveBeenCalled();
    expect(writeConfigFile).not.toHaveBeenCalled();
    expect(refreshPluginRegistry).not.toHaveBeenCalled();
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (2): Last reviewed commit: "fix(cli): prefer exact plugin ids for po..." | Re-trigger Greptile

Comment thread src/cli/plugins-cli.ts Outdated
@clawsweeper

clawsweeper Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge.

Summary
The PR adds pre-write registry validation for plugins enable and plugins disable, missing-id policy tests, selected compatibility alias tests, and a changelog entry.

Reproducibility: yes. Source inspection on current main shows both policy commands call config mutation and replaceConfigFile before any registry lookup, and the linked bug gives deterministic CLI steps; I did not run live commands in this read-only review.

Next step before merge
A narrow PR-branch repair can swap the guard to the registry-backed normalizer and add manifest-alias coverage.

Security
Cleared: Cleared: the diff is limited to CLI validation, tests, and changelog, with no dependency, workflow, package, secret, permission, or code-execution changes.

Review findings

  • [P2] Resolve registry aliases before rejecting plugin ids — src/cli/plugins-cli.ts:538-539
Review details

Best possible solution:

Use the registry-backed canonical plugin-id normalizer for policy command input, then reject only truly unknown ids before config mutation.

Do we have a high-confidence way to reproduce the issue?

Yes. Source inspection on current main shows both policy commands call config mutation and replaceConfigFile before any registry lookup, and the linked bug gives deterministic CLI steps; I did not run live commands in this read-only review.

Is this the best way to solve the issue?

No, not yet. The pre-write validation boundary is right, but the PR should canonicalize through the manifest-aware registry alias contract instead of the static fallback.

Full review comments:

  • [P2] Resolve registry aliases before rejecting plugin ids — src/cli/plugins-cli.ts:538-539
    The guard normalizes with normalizePluginId and then checks only plugin.id. That static fallback misses manifest contribution aliases such as byteplus-plan and stepfun-plan, so valid alias inputs that resolve through the registry normalizer can now fail with Plugin not found instead of writing the canonical plugin entry.
    Confidence: 0.89

Overall correctness: patch is incorrect
Overall confidence: 0.88

Acceptance criteria:

  • pnpm test src/cli/plugins-cli.policy.test.ts src/plugins/plugin-registry.test.ts src/plugins/config-state.test.ts -- --reporter=verbose
  • pnpm exec oxfmt --check --threads=1 src/cli/plugins-cli.ts src/cli/plugins-cli.policy.test.ts CHANGELOG.md
  • git diff --check
  • pnpm check:changed -- --base openclaw/main

What I checked:

  • Current main enable path mutates before validation: plugins enable reads config, calls enablePluginInConfig(cfg, id), then writes the resulting config before any discovered-plugin lookup. (src/cli/plugins-cli.ts:111, 55c738ad4bed)
  • Current main disable path mutates before validation: plugins disable calls setPluginEnabledInConfig(cfg, id, false) and writes config before any discovered-plugin lookup. (src/cli/plugins-cli.ts:149, 55c738ad4bed)
  • Registry aliases are broader than static config-state aliases: createPluginRegistryIdNormalizer maps manifest providers, channels, setup providers, CLI backends, model catalog providers/aliases, and legacy ids to canonical plugin ids. (src/plugins/plugin-registry-id-normalizer.ts:22, 55c738ad4bed)
  • Concrete manifest alias exists: Bundled provider manifests still advertise aliases such as byteplus-plan, stepfun-plan, and volcengine-plan; these are not in normalizePluginId's static fallback list. (extensions/byteplus/openclaw.plugin.json:8, 55c738ad4bed)
  • PR guard uses static normalization plus exact id check: The PR normalizes with normalizePluginId(id) and rejects unless report.plugins contains that exact id, which misses manifest-driven aliases that resolve only through the registry normalizer. (src/cli/plugins-cli.ts:538, 38c7aef33274)
  • Changelog gap resolved: The current PR files include the required user-facing changelog entry for the CLI/plugins behavior change. (CHANGELOG.md:26, 38c7aef33274)

Likely related people:

  • vincentkoc: Prior same-cluster ClawSweeper history and PR discussion tie them to recent plugin registry, config normalization, and registry refresh work in the affected alias contract area. (role: plugin registry and config maintainer; confidence: high; commits: 74e7b8d47b18, 7308e72fac98, 95bc4179442e; files: src/plugins/config-state.ts, src/plugins/status.ts, src/plugins/plugin-registry-id-normalizer.ts)
  • hxy91819: They are assigned/requested on this PR, left the compatibility-id regression review, and authored the follow-up changelog commit on the branch. (role: reviewer and likely follow-up owner; confidence: medium; commits: 38c7aef33274; files: src/cli/plugins-cli.ts, src/cli/plugins-cli.policy.test.ts, CHANGELOG.md)
  • steipete: Prior same-cluster issue history identifies plugin config mutation, allowlist, and enable-path work adjacent to the affected CLI policy surface. (role: adjacent maintainer; confidence: medium; commits: 519517915045, 87603b5c45e5, 47216702f4f7; files: src/plugins/enable.ts, src/cli/plugins-cli.ts, src/config/plugins-allowlist.ts)

Remaining risk / open question:

  • The branch needs one more focused alias-normalization repair and regression test before it is safe to merge.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 55c738ad4bed.

@ai-hpc

ai-hpc commented Apr 28, 2026

Copy link
Copy Markdown
Member Author

@greptile-apps

@ai-hpc

ai-hpc commented Apr 28, 2026

Copy link
Copy Markdown
Member Author

Human verification update for daeda6e8f4b43a03e15a790f4d510f1afc419c7b:

Verified scenarios:

  • pnpm test src/cli/plugins-cli.policy.test.ts -- --reporter=verbose passed: 1 file, 8 tests.
  • pnpm check:changed passed. It selected lanes=all because the rebased fork branch history made the changed-lane detector conservative; completed checks included conflict markers, changelog attributions, guarded/plugin-sdk wildcard re-export guards, runtime sidecar loader guard, tsgo:all, oxlint shards, and runtime import cycles.
  • git diff --check upstream/main...HEAD passed.
  • GitHub currently reports mergeable: true, mergeable_state: clean.

Edge cases checked:

  • Exact plugin IDs still enable/disable and refresh the registry.
  • Nonexistent IDs exit 1 before config writes.
  • Display names are rejected for policy writes.
  • Exact ID wins when another plugin has the same display name.

What I did not verify:

  • This PR prevents new stale plugins.entries.<id> writes for plugins enable and plugins disable; I did not verify cleanup of stale entries already written by older versions.
  • I did not run a live Gateway scenario; this PR is covered by local CLI policy tests.
  • I did not test on macOS or Windows locally.

@ai-hpc
ai-hpc force-pushed the fix/plugins-missing-id-policy branch 2 times, most recently from f762801 to daeda6e Compare April 29, 2026 00:41
@ai-hpc

ai-hpc commented Apr 29, 2026

Copy link
Copy Markdown
Member Author

Hello @vincentkoc
Could you review this PR please ?
Thanks very much.

@hxy91819

Copy link
Copy Markdown
Member

The missing-id guard is directionally right, but the exact-id check in src/cli/plugins-cli.ts:536 / src/cli/plugins-cli.ts:578 regresses current compatibility ids.

The repo still normalizes provider/channel/legacy ids such as openai-codex, google-gemini-cli, and minimax-portal-auth back to canonical plugin ids in src/plugins/config-state.ts:38 and src/plugins/plugin-registry.test.ts:293. Please resolve the user input through the existing alias-aware normalizer before rejecting it, and add command coverage that plugins enable|disable still accept those compatibility ids while still failing for truly unknown ids.

I do not think this is ready for /prepare-pr until that regression is fixed.

@hxy91819 hxy91819 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The missing-id guard is directionally right, but the exact-id check in src/cli/plugins-cli.ts:536 / src/cli/plugins-cli.ts:578 regresses current compatibility ids.

The bug this PR is trying to fix is real: plugins enable <missing-id> / plugins disable <missing-id> should fail before writing stale plugins.entries.<id> config. But I do not think this is the best fix as written, because it also rejects ids that current OpenClaw code and shipped manifests still treat as valid compatibility aliases.

Evidence:

  • src/plugins/config-state.ts:38 still normalizes compatibility ids such as openai-codex, google-gemini-cli, and minimax-portal-auth back to canonical plugin ids.
  • src/plugins/plugin-registry.test.ts:293 proves registry contribution aliases such as provider ids and channel ids normalize back to the owning plugin id.
  • src/plugins/config-state.test.ts:134 locks in legacy plugin-id normalization.
  • extensions/openai/openclaw.plugin.json:7, extensions/google/openclaw.plugin.json:7, and extensions/minimax/openclaw.plugin.json:7 still advertise those compatibility ids in current manifests.

Please resolve the user-supplied id through the existing alias-aware normalizer before rejecting it, then validate the canonical plugin id. Also add command coverage that plugins enable|disable still accept compatibility ids while still failing for truly unknown ids.

Given that regression, I do not think this is ready for /prepare-pr yet.

@ai-hpc

ai-hpc commented Apr 30, 2026

Copy link
Copy Markdown
Member Author

Thanks, fixed in ee7433f52a.

The enable/disable guard now resolves the CLI argument through the existing normalizePluginId path before registry validation and mutation. That preserves compatibility ids such as openai-codex, google-gemini-cli, and minimax-portal-auth, while still rejecting truly unknown ids before any config write.

Added command coverage for both plugins enable and plugins disable accepting those compatibility ids and writing the canonical plugin entry.

Validation run:

  • pnpm test src/cli/plugins-cli.policy.test.ts src/plugins/config-state.test.ts src/plugins/plugin-registry.test.ts -- --reporter=verbose
  • pnpm check:changed --base openclaw/main
  • git diff --check

@ai-hpc
ai-hpc requested a review from hxy91819 April 30, 2026 15:54
@ai-hpc
ai-hpc force-pushed the fix/plugins-missing-id-policy branch from ee5b670 to ee7433f Compare April 30, 2026 16:22
@ai-hpc

ai-hpc commented May 2, 2026

Copy link
Copy Markdown
Member Author

Tested this PR locally.

Before/after regression check:

  • Before: parent commit b79e617ad1; a temporary regression test for plugins enable missing-plugin and plugins disable missing-plugin failed because both commands resolved instead of rejecting.
  • Before behavior was also observed directly: both missing-plugin paths reached writeConfigFile and refreshPluginRegistry.
  • After: PR commit ee7433f52a; the same regression test passed. Both commands reject with Plugin not found: missing-plugin..., and no config write, registry refresh, or enablePluginInConfig call occurs.

@hxy91819
hxy91819 force-pushed the fix/plugins-missing-id-policy branch from 38c7aef to 04a3bf6 Compare May 3, 2026 03:45
@hxy91819
hxy91819 force-pushed the fix/plugins-missing-id-policy branch from 04a3bf6 to f0d3e61 Compare May 3, 2026 03:48
@hxy91819
hxy91819 merged commit 6a3f5d0 into openclaw:main May 3, 2026
78 checks passed
@hxy91819

hxy91819 commented May 3, 2026

Copy link
Copy Markdown
Member

Merged via squash.

Thanks @ai-hpc!

lxe pushed a commit to lxe/openclaw that referenced this pull request May 6, 2026
)

Merged via squash.

Prepared head SHA: f0d3e61
Co-authored-by: ai-hpc <[email protected]>
Co-authored-by: hxy91819 <[email protected]>
Reviewed-by: @hxy91819
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
)

Merged via squash.

Prepared head SHA: f0d3e61
Co-authored-by: ai-hpc <[email protected]>
Co-authored-by: hxy91819 <[email protected]>
Reviewed-by: @hxy91819
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
)

Merged via squash.

Prepared head SHA: f0d3e61
Co-authored-by: ai-hpc <[email protected]>
Co-authored-by: hxy91819 <[email protected]>
Reviewed-by: @hxy91819
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
)

Merged via squash.

Prepared head SHA: f0d3e61
Co-authored-by: ai-hpc <[email protected]>
Co-authored-by: hxy91819 <[email protected]>
Reviewed-by: @hxy91819
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
)

Merged via squash.

Prepared head SHA: f0d3e61
Co-authored-by: ai-hpc <[email protected]>
Co-authored-by: hxy91819 <[email protected]>
Reviewed-by: @hxy91819
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli CLI command changes size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: plugins enable <nonexistent-id> writes a stale plugin config entry and exits 0

2 participants