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)
- Wire the gateway
call <method> dispatcher through the same registry that plugins inspect --runtime walks, so plugin-registered methods resolve.
- 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.
- 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:8427 — registerGatewayMethod type declaration
openclaw gateway --help — advertises call <method> --params <json> as the invocation surface
Environment
openclaw/plugin-sdk2026.6.8 (pinned via pluginpackage.json)definePluginEntry({register})withapi.registerGatewayMethod(name, handler, {scope: "operator.write"})in theregisterbodyTL;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), butopenclaw gateway call my-plugin.doThing --params '{}'returnsINVALID_REQUEST: unknown methodimmediately. 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,registerGatewayMethodreads as the supported surface.Repro
Minimal plugin (
src/index.ts):Install + restart + inspect:
Plugin log confirms registration:
Invoke:
Expected vs actual
openclaw gateway call demo.ping --params '{...}'resolves the method through the active registry the same wayplugins inspect --runtimereports it, invokes the handler in-process, and returns the handler's JSON result.INVALID_REQUEST: unknown methodbefore 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
plugins inspect --runtime --jsonopenclaw gateway call demo.ping(no params)openclaw gateway call demo.ping --params '{}'openclaw gateway call demo.ping --params '...'withscopeomitted on registrationgateway.status) called the same waySo the dispatcher path that resolves built-in methods is not the path that sees plugin-registered methods, even though
plugins inspect --runtimewalks something that does.Workaround
Bundled OpenClaw plugins (
workboard,browser, ...) sidestep this by exposing a thinapi.registerCli(...)wrapper that invokes the handler in-process via the gateway's CLI router instead of the RPC dispatcher:Then cron / external callers use
openclaw demo ping --jsoninstead ofopenclaw gateway call demo.ping.This works, but it makes
registerGatewayMethodeffectively 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
registerGatewayMethodas the canonical RPC surface (types-Tcpca_5M.d.ts:8427) andopenclaw 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 beingunknown method, which reads like a typo and sends authors to re-check the method name first.Concrete symptoms downstream:
openclaw gateway call <plugin>.<method>(the documented pattern) fail uniformly.registerClipattern by reading the bundledworkboard/browserplugin sources — neither is referenced in the plugin SDK docs.Suggested fixes (any one would resolve the surface)
call <method>dispatcher through the same registry thatplugins inspect --runtimewalks, so plugin-registered methods resolve.gateway calldispatcher, return a clearer error (PLUGIN_METHOD_NOT_DISPATCHABLE: use openclaw <plugin> <subcommand>or similar) and document the boundary.api.registerCli(...)as the canonical pattern for externally-invokable plugin operations and explicitly deprecateregisterGatewayMethod(or scope it to in-process callers only).References
dist/cli-registration-snxxIFVt.js— plugin SDK CLI registration helper used by the workboard/browser patterntypes-Tcpca_5M.d.ts:8427—registerGatewayMethodtype declarationopenclaw gateway --help— advertisescall <method> --params <json>as the invocation surface