Skip to content

[Bug]: openclaw gateway call <plugin-method> returns "unknown method" for plugin-registered RPC methods, even though plugins inspect --runtime confirms the registration #94127

Description

@BryceMurray

Environment

  • OpenClaw: 2026.6.8 (gateway + CLI)
  • Plugin SDK: openclaw/plugin-sdk 2026.6.8 (pinned via plugin package.json)
  • Plugin shape: definePluginEntry({register}) with api.registerGatewayMethod(name, handler, {scope: "operator.write"}) in the register body
  • Host: single-VPS Linux deployment, no container

TL;DR

A plugin registers a gateway RPC method through api.registerGatewayMethod("my-plugin.doThing", handler, {scope: "operator.write"}). The registration is reported in two places (plugin log + openclaw plugins inspect <plugin> --runtime --json), but openclaw gateway call my-plugin.doThing --params '{}' returns INVALID_REQUEST: unknown method immediately. The handler is never invoked.

The result is that plugin-registered RPC methods are unreachable from external invocation paths (CLI, cron), forcing every plugin author to fall back to the api.registerCli(...) workboard-style wrapper. That's the de facto workaround in the bundled plugins (workboard, browser) but it's nowhere in the plugin-SDK docs as the canonical path. From the docs, registerGatewayMethod reads as the supported surface.

Repro

Minimal plugin (src/index.ts):

import { definePluginEntry } from "openclaw/plugin-sdk";

export default definePluginEntry({
  id: "demo",
  name: "Demo",
  register(api) {
    if (typeof api.registerGatewayMethod === "function") {
      api.registerGatewayMethod(
        "demo.ping",
        async (params) => ({ ok: true, echo: params, ts: Date.now() }),
        { scope: "operator.write" },
      );
      api.logger?.info?.("[demo] registered gateway method demo.ping");
    }
  },
});

Install + restart + inspect:

$ openclaw plugins install --link ./demo-plugin/
$ openclaw gateway restart
$ openclaw plugins inspect demo --runtime --json | jq '.gatewayMethods'
[
  {
    "name": "demo.ping",
    "scope": "operator.write",
    "registeredAt": "2026-06-17T...Z"
  }
]

Plugin log confirms registration:

[demo] registered gateway method demo.ping

Invoke:

$ openclaw gateway call demo.ping --params '{"hello":"world"}'
Error: INVALID_REQUEST: unknown method

Expected vs actual

  • Expected: openclaw gateway call demo.ping --params '{...}' resolves the method through the active registry the same way plugins inspect --runtime reports it, invokes the handler in-process, and returns the handler's JSON result.
  • Actual: dispatcher rejects with INVALID_REQUEST: unknown method before the handler is reached. No log line on the gateway side indicates the lookup attempt or what registry it was searched against.

What's been tested

Check Result
Method appears in plugins inspect --runtime --json ✅ yes
Method appears in plugin's own boot log ✅ yes
Handler invoked when called from in-process code (mock SDK in test harness) ✅ yes
openclaw gateway call demo.ping (no params) ❌ unknown method
openclaw gateway call demo.ping --params '{}' ❌ unknown method
openclaw gateway call demo.ping --params '...' with scope omitted on registration ❌ unknown method
Built-in gateway methods (e.g. gateway.status) called the same way ✅ resolves
Gateway restart between install and call ✅ done; same result

So the dispatcher path that resolves built-in methods is not the path that sees plugin-registered methods, even though plugins inspect --runtime walks something that does.

Workaround

Bundled OpenClaw plugins (workboard, browser, ...) sidestep this by exposing a thin api.registerCli(...) wrapper that invokes the handler in-process via the gateway's CLI router instead of the RPC dispatcher:

if (typeof (api as any).registerCli === "function") {
  api.registerCli(
    async ({ program }) => {
      program
        .command("demo")
        .command("ping")
        .option("--json", "Print result as JSON", false)
        .action(async (opts) => {
          const result = await pingHandler({});
          if (opts.json) process.stdout.write(JSON.stringify(result) + "\n");
        });
    },
    {
      descriptors: [
        { name: "demo", description: "Demo plugin", hasSubcommands: true },
      ],
    },
  );
}

Then cron / external callers use openclaw demo ping --json instead of openclaw gateway call demo.ping.

This works, but it makes registerGatewayMethod effectively dead surface and forces every plugin that wants external invocation to learn two registration patterns (gateway method and CLI) with no documented guidance on which to use.

Why it matters

The plugin SDK type definitions document registerGatewayMethod as the canonical RPC surface (types-Tcpca_5M.d.ts:8427) and openclaw gateway call <method> is the documented invocation path (openclaw gateway --help). A plugin author following the docs ends up with code that compiles, registers cleanly, and silently fails at runtime — with the only diagnostic being unknown method, which reads like a typo and sends authors to re-check the method name first.

Concrete symptoms downstream:

  • Cron jobs migrating from direct Python invocation to openclaw gateway call <plugin>.<method> (the documented pattern) fail uniformly.
  • The fix path requires plugin authors to discover the registerCli pattern by reading the bundled workboard/browser plugin sources — neither is referenced in the plugin SDK docs.
  • Hot-loaded plugins that worked under an earlier SDK version may regress silently after an OpenClaw upgrade if this is a new restriction.

Suggested fixes (any one would resolve the surface)

  1. Wire the gateway call <method> dispatcher through the same registry that plugins inspect --runtime walks, so plugin-registered methods resolve.
  2. If plugin-registered methods are intentionally off the gateway call dispatcher, return a clearer error (PLUGIN_METHOD_NOT_DISPATCHABLE: use openclaw <plugin> <subcommand> or similar) and document the boundary.
  3. Document api.registerCli(...) as the canonical pattern for externally-invokable plugin operations and explicitly deprecate registerGatewayMethod (or scope it to in-process callers only).

References

  • dist/cli-registration-snxxIFVt.js — plugin SDK CLI registration helper used by the workboard/browser pattern
  • types-Tcpca_5M.d.ts:8427registerGatewayMethod type declaration
  • openclaw gateway --help — advertises call <method> --params <json> as the invocation surface

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:linked-pr-openClawSweeper found an open linked pull request for this issue.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:otherThis issue has meaningful maintainer-visible impact outside the owned taxonomy.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions