Skip to content

fix(cli): clarify error when unknown subcommand is actually an agent tool name (#77214)#77221

Closed
100yenadmin wants to merge 1 commit into
openclaw:mainfrom
electricsheephq:fix/77214-cli-tool-name-suggestion
Closed

fix(cli): clarify error when unknown subcommand is actually an agent tool name (#77214)#77221
100yenadmin wants to merge 1 commit into
openclaw:mainfrom
electricsheephq:fix/77214-cli-tool-name-suggestion

Conversation

@100yenadmin

Copy link
Copy Markdown
Contributor

Summary

  • When openclaw <name> does not match a CLI subcommand or a plugin id, the unknown-subcommand handler now first looks up <name> against the runtime plugin tool registry. If <name> is an agent tool registered by a loaded plugin (e.g. lcm_recent from lossless-claw), emit a clear "this is an agent tool, not a CLI subcommand" error pointing at model tool-use instead of the misleading plugins.allow suggestion.
  • The previous error told the user to add the tool name to plugins.allow, but plugins.allow accepts plugin ids (not tool names), and config patches against it are rejected by the protected-config-paths guard. In the wild this sent an agent down 3 restart cycles trying to "fix" config that was never the problem.
  • Fall-throughs are preserved: if <name> is not a registered tool, the existing plugins.allow and plugins.entries.<id>.enabled suggestions are emitted unchanged. The new branch only fires when the tool-registry lookup matches.

Reproduction

Before:

[openclaw] Failed to start CLI: Error: The `openclaw lcm_recent` command is unavailable
because `plugins.allow` excludes "lcm_recent". Add "lcm_recent" to `plugins.allow`
if you want that bundled plugin CLI surface.

After:

"lcm_recent" is an agent tool registered by the "lossless-claw" plugin, not a CLI
subcommand. Use it from an agent turn (model tool-use), not the CLI. Run
`openclaw --help` to see available CLI subcommands.

Implementation

  • New resolveManifestToolOwnerInRegistry in src/plugins/manifest-command-aliases.ts walks plugins[].contracts.tools (the same field the agent dispatch uses) and returns {toolName, pluginId} for the owning plugin.
  • PluginManifestCommandAliasRegistry is extended with an optional contracts?: { tools?: readonly string[] } per plugin entry. The runtime PluginManifestRegistry already populates this, so production wiring is structural.
  • resolveMissingPluginCommandMessage (in src/cli/run-main-policy.ts) gets a parallel resolveToolOwner callback hook (mirroring resolveCommandAliasOwner) and consults the tool registry just before the plugins.allow excludes branch.
  • src/cli/run-main.ts passes the new resolveManifestToolOwner runtime resolver into the policy call.

Validation

  • pnpm exec vitest run src/cli/run-main.test.ts src/plugins/manifest-command-aliases.test.ts -- 33 passed (3 new in run-main.test.ts, 1 new in manifest-command-aliases.test.ts).
  • pnpm exec vitest run src/cli/run-main.exit.test.ts src/cli/run-main.profile-env.test.ts -- 79 passed (sanity check on adjacent test files).
  • pnpm exec oxlint --type-aware src/cli/run-main-policy.ts src/cli/run-main.ts src/cli/run-main.test.ts src/plugins/manifest-command-aliases.ts src/plugins/manifest-command-aliases.runtime.ts src/plugins/manifest-command-aliases.test.ts -- 0 warnings, 0 errors.
  • pnpm tsgo:core -- clean.
  • pnpm check:changelog-attributions -- clean.

Closes #77214.

Copilot AI review requested due to automatic review settings May 4, 2026 09:08
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@clawsweeper

clawsweeper Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge.

Summary
The PR adds an unknown-subcommand diagnostic that maps manifest-declared agent tool names to owning plugins, with softer wording for manifest-only availability plus tests and a changelog entry.

Reproducibility: yes. from source inspection. Current main can emit the misleading plugins.allow message for an agent-tool-looking root command, and the PR's remaining denylist mismatch is visible by comparing its new owner guard with current runtime tool discovery policy.

Real behavior proof
Needs real behavior proof before merge: The PR includes tests and example output, but no live after-fix CLI output, terminal screenshot, recording, linked artifact, or redacted log showing the changed behavior in a real setup. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, ask a maintainer to comment @clawsweeper re-review.

Next step before merge
The contributor needs to fix the denylist correctness blocker and add real behavior proof; automation should not take this over while the external PR proof gate is missing.

Security
Cleared: The diff is limited to CLI diagnostic logic, manifest metadata lookup helpers, tests, and changelog text; no concrete security or supply-chain regression was found.

Review findings

  • [P2] Honor plugin denylist before emitting tool diagnostics — src/cli/run-main-policy.ts:197-199
Review details

Best possible solution:

Keep the CLI diagnostic, but base the tool-owner branch on the same effective plugin/tool policy as runtime discovery, including plugins.deny, then add focused regression coverage and live CLI proof for the intended openclaw <tool-name> message.

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

Yes from source inspection. Current main can emit the misleading plugins.allow message for an agent-tool-looking root command, and the PR's remaining denylist mismatch is visible by comparing its new owner guard with current runtime tool discovery policy.

Is this the best way to solve the issue?

No. The requested diagnostic is the right narrow fix, but this patch should also honor plugins.deny for the owning plugin and provide real after-fix CLI proof before merge.

Full review comments:

  • [P2] Honor plugin denylist before emitting tool diagnostics — src/cli/run-main-policy.ts:197-199
    The new owner guard only checks plugins.allow and entries.<id>.enabled; it never checks plugins.deny. Because the runtime resolver's isManifestPluginAvailableForControlPlane returns true for bundled plugins, a config such as a denylisted bundled Feishu plugin with config signals present can still produce the new agent-tool message even though runtime tool discovery skips denylisted plugins. Please include the owning plugin in the same denylist policy before returning this diagnostic.
    Confidence: 0.88

Overall correctness: patch is incorrect
Overall confidence: 0.86

What I checked:

  • Current-main behavior is source-reproducible: On current main, resolveMissingPluginCommandMessage falls through to the plugins.allow excludes message for an unknown root command name when a restrictive plugins.allow does not include that name. (src/cli/run-main-policy.ts:174, eaaef2dbf871)
  • PR owner guard omits plugins.deny: The new PR branch checks the owning plugin's entries.<id>.enabled and plugins.allow, but not plugins.deny, before emitting the agent-tool diagnostic. (src/cli/run-main-policy.ts:197, ae62a99b3537)
  • Bundled plugin availability helper bypasses config denylist: isManifestPluginAvailableForControlPlane returns true for bundled plugins before consulting installed-plugin policy, so the PR runtime resolver does not filter plugins.deny for bundled tool owners. (src/plugins/manifest-contract-eligibility.ts:20, eaaef2dbf871)
  • Runtime tool discovery does enforce the denylist: The current runtime tool discovery path explicitly skips plugins whose ids appear in normalized plugins.deny, so the PR diagnostic can diverge from effective runtime tool availability. (src/plugins/tools.ts:453, eaaef2dbf871)
  • Prior review and author follow-up identified active-availability filtering as the key risk: The existing ClawSweeper/Copilot review and the author's follow-up both focused on avoiding false tool attribution for disabled, denied, or not-actually-registered tools; the latest patch handles allowlist/entry-disabled cases but not bundled plugin denylist.
  • Real behavior proof is still absent: The PR body lists tests, lint, typecheck, and example before/after text, but no copied live openclaw lcm_recent output, screenshot, terminal recording, or log from a real after-fix setup. (ae62a99b3537)

Likely related people:

  • steipete: Recent GitHub commit history for the central CLI diagnostic and manifest-alias files includes disabled plugin command alias diagnostics and helper extraction by Peter Steinberger. (role: recent maintainer and introduced adjacent behavior; confidence: high; commits: d763b8385467, 777c6f758091, 82a8006f7710; files: src/cli/run-main-policy.ts, src/plugins/manifest-command-aliases.ts)
  • vincentkoc: Recent src/plugins/tools.ts history includes optional tool metadata, manifest optional sibling handling, and plugin tool denylist fixes by Vincent Koc, which are directly adjacent to the availability semantics this PR must match. (role: adjacent tool-availability maintainer; confidence: medium; commits: 571d75aab351, 443f7035a2e5, 09e7eb6687a1; files: src/plugins/tools.ts)
  • shakkernerd: Recent GitHub history for manifest contract eligibility includes manifest tool owner discovery and related control-plane availability work by Shakker. (role: introduced adjacent manifest tool discovery behavior; confidence: medium; commits: e6825fceaa04, dfde770a3aed; files: src/plugins/manifest-contract-eligibility.ts, src/plugins/tools.ts)

Remaining risk / open question:

  • The PR can still tell a user that a denylisted bundled plugin provides or registered an agent tool even though runtime tool discovery will not materialize that plugin's tools.
  • The contributor has not provided real after-fix behavior proof from a live CLI setup, so the real-behavior merge gate remains open.

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR tries to improve CLI diagnostics when openclaw <name> is not a real CLI subcommand by distinguishing agent-tool names from plugin command names. It extends plugin manifest lookup so the unknown-command path can point users toward model tool-use instead of the existing plugins.allow suggestion.

Changes:

  • Added manifest-level tool-owner lookup alongside existing command-alias lookup.
  • Wired the new tool-owner resolver into CLI unknown-subcommand handling.
  • Added focused tests for tool-name detection and updated the changelog.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/plugins/manifest-command-aliases.ts Adds manifest-based tool-owner lookup and extends the registry shape with contracts.tools.
src/plugins/manifest-command-aliases.test.ts Adds unit coverage for manifest-based tool-owner resolution.
src/plugins/manifest-command-aliases.runtime.ts Exposes a runtime helper that loads manifest metadata and resolves tool owners.
src/cli/run-main.ts Passes the new runtime tool-owner resolver into unknown-command policy handling.
src/cli/run-main.test.ts Adds tests for the new agent-tool error messaging behavior.
src/cli/run-main-policy.ts Introduces the new “agent tool, not CLI subcommand” error branch.
CHANGELOG.md Documents the CLI diagnostic change.

Comment thread src/cli/run-main-policy.ts Outdated
Comment thread src/plugins/manifest-command-aliases.ts
@100yenadmin

100yenadmin commented May 8, 2026

Copy link
Copy Markdown
Contributor Author

The Copilot threads' correctness concern is real — verified against pr77221 head. The diagnostic fires for any tool name listed in any plugin manifest's static contracts.tools, regardless of whether that plugin is loaded or whether the tool is actually registered at runtime.

Timing context. resolveMissingPluginCommandMessage runs in src/cli/run-main.ts (~lines 642-655) after registerPluginCliCommandsFromValidatedConfig (manifest-level only — never instantiates extensions) but before program.parseAsync. None of loadOpenClawPlugins / activatePluginRegistry / setActivePluginRegistry is called on this path; they only run inside agent / channel / web-search / status / providers runtimes. So getActivePluginRegistry() is null here — the fix has to work from the manifest snapshot.

The unfiltered registry hits real cases. extensions/feishu/openclaw.plugin.json lists feishu_chat, feishu_doc, etc. in contracts.tools with manifest configSignals for appId/appSecret. But extensions/feishu/src/chat.ts:124-138 only registers them when listEnabledFeishuAccounts(...) is non-empty AND resolveToolsConfig(...).chat !== false — both runtime-only. Same shape in perm.ts, docx.ts, drive.ts, bitable.ts, wiki.ts. A user with Feishu disabled who types openclaw feishu_chat gets "registered by 'feishu' plugin" today, even though the runtime never registered it.

Suggested fix shape.

  1. Filter the registry passed to resolveManifestToolOwnerInRegistry through existing helpers — no new infrastructure needed:

    • isManifestPluginAvailableForControlPlane (src/plugins/manifest-contract-eligibility.ts:12) — covers plugins.allow / deny / entries[id].enabled / installed-index. (Note: it early-returns true for origin === "bundled", bypassing plugins.allow for bundled plugins — worth flagging in the same patch if you want full coverage.)
    • hasManifestToolAvailability (src/plugins/manifest-tool-availability.ts:257) — covers per-tool toolMetadata.configSignals like Feishu's appId/appSecret gate.
  2. Reorder branches in src/cli/run-main-policy.ts. The new toolOwner branch at :160-170 is reached before the existing plugins.allow / entries.enabled filters at :175-194, so those filters never apply to it — wire them through, or move the filters up so they cover the new branch.

  3. Soften the wording. Manifest-level filtering is necessarily an over-approximation for the Feishu family (per-account enabled flags and per-tool tools.<x> toggles are runtime-only and can't be expressed as configSignals). When ownership is only manifest-provable, emit something like:

    "<x>" may be provided by the "<id>" plugin if it is loaded; it is an agent tool name, not a CLI subcommand. Run \openclaw --help` to see CLI subcommands.`

    instead of asserting "registered by".

  4. Same lack of filtering affects the runtime-slash branch via resolveManifestCommandAliasOwnerInRegistry (src/plugins/manifest-command-aliases.ts:96-122): the early return at run-main-policy.ts:156 for the slash kind bypasses plugins.allow for parentPluginId. Worth fixing in the same patch.

  5. Rebase to clear the CHANGELOG.md 3-way conflict against current main.

Once that lands the two outstanding Copilot threads should resolve naturally.

Re-review progress:

…tool name

When `openclaw <name>` does not match a CLI subcommand or a plugin id,
the unknown-subcommand handler now first looks up <name> against the
manifest plugin tool registry. If <name> is an agent tool declared by a
plugin (e.g. `lcm_recent` from `lossless-claw`), emit a clear 'this is
an agent tool, not a CLI subcommand' error pointing at model tool-use
instead of the misleading `plugins.allow` suggestion.

The previous error told the user to add the tool name to
`plugins.allow`, but `plugins.allow` accepts plugin ids (not tool
names), and config patches against it are rejected by the
protected-config-paths guard. In the wild this sent an agent down 3
restart cycles trying to 'fix' config that was never the problem.

Filtering: the runtime resolver consults the full manifest snapshot and
filters through `isManifestPluginAvailableForControlPlane` (covering
`plugins.allow`/`plugins.deny`/`plugins.entries[id].enabled`/installed
index) plus per-tool `hasManifestToolAvailability` (covering
`toolMetadata.configSignals` like Feishu's `appId`/`appSecret` gate).
The diagnostic also re-checks `plugins.allow` and
`plugins.entries[X].enabled` for the OWNING plugin before emitting, so
denied/disabled plugins are not falsely attributed.

Wording: when ownership is only manifest-provable (per-account
`enabled` flags and per-tool toggles in the Feishu family are
runtime-only and not declarable as configSignals, so the manifest is
necessarily an over-approximation), emit a softer 'may be provided by
the X plugin' message instead of asserting 'registered by'. The strong
wording is reserved for the case where both control-plane availability
and tool availability pass.

Fall-throughs: if <name> is not a registered tool, the existing
`plugins.allow` and `plugins.entries.<id>.enabled` suggestions are
emitted unchanged. The new branch only fires when the tool-registry
lookup matches AND the owning plugin passes availability filters.

Closes #77214.
@100yenadmin 100yenadmin force-pushed the fix/77214-cli-tool-name-suggestion branch from 2852a98 to ae62a99 Compare May 8, 2026 10:04
@openclaw-barnacle openclaw-barnacle Bot added triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. size: M and removed size: S labels May 8, 2026
@steipete

steipete commented May 9, 2026

Copy link
Copy Markdown
Contributor

Thanks @100yenadmin. I could not push the final rebase/fixup back to the fork (403), so I landed this via maintainer replacement #79693.

It preserves your CLI/tool diagnostic fix, softens the wording to avoid overclaiming runtime registration, and adds the missing early proxy-preflight path so live openclaw lcm_recent now reports the agent-tool diagnostic instead of plugins.allow/generic unknown-command guidance.

Landed in 0c50957.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli CLI command changes size: M triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CLI suggests plugins.allow for unknown subcommands when input is actually an agent tool name

3 participants