Skip to content

[Bug]: Gateway WS can return unknown method for plugin gateway methods visible in inspect/listings #54783

@xmoxmo

Description

@xmoxmo

Bug type

Behavior bug (incorrect output/state without crash)

Summary

Plugin gateway methods can be visible in plugins inspect / gateway method listings while the live gateway WebSocket still returns unknown method, which strongly suggests the WS handler table can diverge from the active plugin registry after secondary plugin loads / runtime registry changes.

Steps to reproduce

  1. Run a plugin-backed gateway method provider (repro observed with the bncr channel plugin exposing bncr.connect, bncr.inbound, etc.).
  2. Confirm the plugin is loaded:
    • openclaw plugins inspect bncr
  3. Confirm the gateway method is listed in plugin metadata / gateway method output.
  4. In the failure state, call the live gateway over WS (or use the affected client flow) and observe:
    • unknown method: bncr.connect
  5. Restarting the gateway restores behavior temporarily.

Expected behavior

If a plugin gateway method is listed in the loaded plugin metadata / gateway method listings, the live gateway WebSocket should dispatch to the same method handler instead of returning unknown method.

Actual behavior

Observed failure mode:

  • openclaw plugins inspect bncr shows the plugin as loaded and lists:
    • bncr.connect
    • bncr.inbound
    • bncr.activity
    • bncr.ack
    • bncr.diagnostics
    • bncr.file.*
  • In the broken state, the client still receives:
    • unknown method: bncr.connect
  • Gateway restart restores the method temporarily.

This looks like a process-level registry / handler divergence rather than a session-level issue.

OpenClaw version

2026.3.23-2 (7ffe7e4)

Operating system

Debian Linux (x64)

Install method

npm global

Model

N/A (gateway/plugin registry issue)

Provider / routing chain

Gateway WS → plugin-backed channel method (bncr.connect)

Additional provider/model setup details

The affected plugin is loaded from a local workspace path:

  • ~/.openclaw/workspace/plugins/bncr/index.ts

The plugin itself was hardened locally to make registration idempotent per API instance and to rebind its singleton bridge to the current API on re-registration. That reduced plugin-side ambiguity but does not eliminate the host-side failure mode.

Logs, screenshots, and evidence

Evidence 1: live gateway dispatch returns unknown method based on extraHandlers

From the compiled gateway code (dist/gateway-cli-*.js):

const handler = opts.extraHandlers?.[req.method] ?? coreGatewayHandlers[req.method];
if (!handler) {
  respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, `unknown method: ${req.method}`));
  return;
}

This means the live WS path depends on extraHandlers, not just on method listings.

Evidence 2: method listing comes from a different source

Also from the compiled gateway code:

function listGatewayMethods() {
  const channelMethods = listChannelPlugins().flatMap((plugin) => plugin.gatewayMethods ?? []);
  return Array.from(new Set([...BASE_METHODS, ...channelMethods]));
}

This means the method list is built from plugin declarations / channel plugin metadata, not from the same extraHandlers object used by WS dispatch.

Evidence 3: WS extraHandlers is constructed from a plugin registry snapshot

extraHandlers: {
  ...pluginRegistry.gatewayHandlers,
  ...execApprovalHandlers,
  ...secretsHandlers
}

Because this spreads pluginRegistry.gatewayHandlers into a new object during gateway setup, it appears to behave like a snapshot rather than a live view of the registry.

Evidence 4: active plugin registry is globally replaceable

From runtime code:

function setActivePluginRegistry(registry, cacheKey) {
  state.registry = registry;
  state.key = cacheKey ?? null;
  state.version += 1;
}

So the active registry can change later, while the WS handler table may still be using an earlier expanded extraHandlers object.

Evidence 5: resolvePluginTools() can conditionally trigger a plugin reload path

const activeRegistry = getActivePluginRegistry();
const registry = getActivePluginRegistryKey() && activeRegistry ? activeRegistry : loadOpenClawPlugins(...);

This suggests certain secondary paths (tool discovery / agent tool resolution / related runtime flows) can still take a reload path instead of always reusing the current active registry.

Local verification done

On the affected host, the following remained healthy in the non-broken state:

  • openclaw plugins inspect bncr → loaded, methods present
  • openclaw gateway call bncr.connect --json --params '{}' → succeeds when healthy
  • openclaw gateway call tools.catalog --json --params '{"agentId":"main","includePlugins":true}' alone did not immediately reproduce the failure

So this does not appear to be “every plugin tool lookup always breaks the registry”, but rather a more conditional runtime divergence.

Impact and severity

  • Affected: plugin-backed gateway methods / channel methods
  • Severity: High when it happens, because a loaded plugin can become partially unreachable through the live gateway WS API
  • Consequence: channel/client links can fail even though plugin inspection still looks healthy

Additional information

This appears closely related to existing registry-divergence issues and fixes:

The pattern looks similar to #45606, but for gateway WS methods instead of HTTP routes: runtime dispatch appears to consume a handler table that can diverge from the later active plugin registry / declared method listings.

Suggested fix direction

Likely one of these (or a combination):

  1. Make WS method dispatch consult the live plugin gateway handler registry instead of a startup-time expanded extraHandlers snapshot.
  2. Rebind / refresh the gateway WS plugin handler table whenever the active plugin registry changes.
  3. Audit secondary loadOpenClawPlugins() paths to ensure they do not replace the active runtime registry used by live gateway dispatch unless all dependent runtime tables are updated too.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions