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
- 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 }));
}
});
}
openclaw plugins list --json confirms: httpHandlers: 1, status: "loaded", diagnostics: []
curl -i -X POST http://127.0.0.1:18789/my-plugin/inbound returns 405 Method Not Allowed (no Allow header)
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
Description
Plugin HTTP routes registered via
api.registerHttpRoute()are unreachable in 2026.3.1 becausehandleControlUiHttpRequest(SPA catch-all) runs beforehandlePluginRequestin the gateway request chain.The same plugin + route works correctly on 2026.2.26.
Steps to Reproduce
register():openclaw plugins list --jsonconfirms:httpHandlers: 1,status: "loaded",diagnostics: []curl -i -X POST http://127.0.0.1:18789/my-plugin/inboundreturns 405 Method Not Allowed (noAllowheader)curl -i http://127.0.0.1:18789/my-plugin/inboundreturns 200 OK with Control UI HTMLRoot Cause
In
createGatewayHttpServer(gateway-cli), the request handling order is:handleControlUiHttpRequestserves 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) beforehandleControlUiHttpRequestin the request chain.Possible Workaround
Set
gateway.controlUi.basePathto a specific prefix (e.g./ui) so the catch-all doesn't match plugin paths. (Untested)Environment