Skip to content

[2026.3.1 regression] Control UI handler runs before plugin HTTP routes — plugin routes unreachable #31766

@FayAndXan

Description

@FayAndXan

Description

Plugin HTTP routes registered via api.registerHttpRoute() are unreachable in 2026.3.1 because handleControlUiHttpRequest (SPA catch-all) runs before handlePluginRequest in the gateway request chain.

The same plugin + route works correctly on 2026.2.26.

Steps to Reproduce

  1. Register a plugin HTTP route synchronously in register():
register(api) {
  api.registerHttpRoute({
    path: "/my-plugin/inbound",
    handler: async (req, res) => {
      res.writeHead(200, { "Content-Type": "application/json" });
      res.end(JSON.stringify({ ok: true }));
    }
  });
}
  1. openclaw plugins list --json confirms: httpHandlers: 1, status: "loaded", diagnostics: []
  2. curl -i -X POST http://127.0.0.1:18789/my-plugin/inbound returns 405 Method Not Allowed (no Allow header)
  3. curl -i http://127.0.0.1:18789/my-plugin/inbound returns 200 OK with Control UI HTML

Root Cause

In createGatewayHttpServer (gateway-cli), the request handling order is:

1. handleA2uiHttpRequest
2. canvasHost.handleHttpRequest
3. handleControlUiHttpRequest   <-- SPA catch-all, matches ALL paths
4. handlePluginRequest           <-- never reached for matching paths

handleControlUiHttpRequest serves HTML for any GET request and returns 405 for POST/PUT/etc on any path. Since it runs before plugin routes, plugin HTTP endpoints are effectively shadowed.

Expected Behavior

Plugin HTTP routes should have priority over the Control UI catch-all, matching the 2026.2.26 behavior.

Suggested Fix

Move handlePluginRequest (with its auth check) before handleControlUiHttpRequest in the request chain.

Possible Workaround

Set gateway.controlUi.basePath to a specific prefix (e.g. /ui) so the catch-all doesn't match plugin paths. (Untested)

Environment

  • OpenClaw version: 2026.3.1
  • OS: Linux (Debian 12, Hetzner VPS)
  • Node: v22.22.0
  • Working version: 2026.2.26

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