-
-
Notifications
You must be signed in to change notification settings - Fork 69.3k
[Bug]: Plugin HTTP routes invisible to gateway — registry desynchronization in createGatewayPluginRequestHandler #47041
Description
Bug type
Behavior bug (incorrect output/state without crash)
Summary
createGatewayPluginRequestHandler captures a stale plugin registry reference at HTTP server creation time. Plugin channels (e.g. BlueBubbles) register their webhook HTTP routes into a different registry instance via requireActivePluginRegistry(), making all plugin HTTP routes invisible to the gateway's request handler. POST/PUT to registered webhook paths return 404.
Steps to reproduce
- Install OpenClaw v2026.3.13 via Homebrew (
npm i -g openclaw). - Configure a BlueBubbles channel with
webhookPath: "/bluebubbles-webhook". - Start the gateway:
openclaw gateway start. - Confirm logs show:
[bluebubbles] [default] BlueBubbles webhook listening on /bluebubbles-webhook. - Confirm channel status is OK:
openclaw status --deep→ BlueBubbles: OK. - Send a POST request to the webhook:
curl -s -o /dev/null -w "%{http_code}" -X POST http://127.0.0.1:18789/bluebubbles-webhook \ -H "Content-Type: application/json" -d '{"type":"new-message","data":{}}'
- Observe: 404 Not Found (expected: 401 Unauthorized or 200).
- Send a PUT request (should be 405 Method Not Allowed if the route is registered):
curl -s -o /dev/null -w "%{http_code}" -X PUT http://127.0.0.1:18789/bluebubbles-webhook - Observe: 404 Not Found (expected: 405).
This reproduces on every fresh process start — it is not caused by hot-reload or config editing.
Expected behavior
- POST to
/bluebubbles-webhookwithout credentials → 401 Unauthorized (route matched, auth failed). - PUT to
/bluebubbles-webhook→ 405 Method Not Allowed (route matched, wrong method). - POST with valid
?password=→ 200 OK (webhook processed).
Actual behavior
- POST and PUT to
/bluebubbles-webhook→ 404 Not Found. - Gateway logs confirm
BlueBubbles webhook listening on /bluebubbles-webhook— the channel believes it registered the route. - Injected diagnostics into
createGatewayPluginRequestHandlerconfirmregistry.httpRoutes.length === 0on every request, while the active plugin registry (viagetActivePluginRegistry()) contains the registered routes.
OpenClaw version
2026.3.13
Operating system
macOS 26.3.1 (Tahoe) arm64
Install method
Homebrew (npm i -g openclaw)
Model
anthropic/claude-opus-4-6 (not model-dependent — this is a gateway HTTP routing bug)
Provider / routing chain
N/A — bug is in the gateway HTTP server layer, not the model/provider chain
Config file / key location
~/.openclaw/openclaw.json ; channels.bluebubbles.webhookPath ; plugins.allow includes "bluebubbles"
Additional provider/model setup details
This bug affects any channel plugin that registers HTTP routes via registerPluginHttpRoute / registerWebhookTargetWithPluginRoute. BlueBubbles is the primary affected channel but any plugin using the same pattern would have the same issue.
Logs, screenshots, and evidence
Diagnostic injected into createGatewayPluginRequestHandler (gateway-cli-Ol-vpIk7.js):
[DIAG-PLUGIN] routes=0 routePaths= url=/bluebubbles-webhook
[DIAG] no stage handled method=POST url=/bluebubbles-webhook stageCount=11
stageNames=hooks,tools-invoke,slack,canvas-auth,a2ui,canvas-http,
plugin-auth,plugin-http,control-ui-avatar,control-ui-http,gateway-probes
The routes=0 proves the captured registry object has no httpRoutes, while the channel successfully called registerPluginHttpRoute (which pushed routes into a different registry obtained via requireActivePluginRegistry()).
Root cause in code:
In gateway-cli-Ol-vpIk7.js, createGatewayPluginRequestHandler (line ~19542) captures the registry at closure creation time:
function createGatewayPluginRequestHandler(params) {
const { registry, log } = params; // ← captured once
return async (req, res, ...) => {
if ((registry.httpRoutes ?? []).length === 0) return false; // ← reads stale ref
const matchedRoutes = findMatchingPluginHttpRoutes(registry, pathContext);
// ...
};
}But registerPluginHttpRoute (called by channel plugins during startup) uses requireActivePluginRegistry() to find the registry:
function registerPluginHttpRoute(params) {
const registry = params.registry ?? requireActivePluginRegistry(); // ← different object
// ...
routes.push(entry); // ← pushed to the active registry, not the captured one
}loadOpenClawPlugins is called multiple times during gateway startup (directly in loadGatewayPlugins and indirectly via loadSchemaWithPlugins for config validation). Each call may produce a different registry object and update the active pointer via setActivePluginRegistry(). The HTTP server is created with one registry, but channel startup happens later when the active registry has already been replaced.
Working patch (applied and verified):
function createGatewayPluginRequestHandler(params) {
const { registry: _fallbackRegistry, log } = params;
return async (req, res, providedPathContext, dispatchContext) => {
const registry = getActivePluginRegistry() ?? _fallbackRegistry; // ← live lookup
if ((registry.httpRoutes ?? []).length === 0) return false;
// ... rest unchanged
};
}Same fix applied to shouldEnforcePluginGatewayAuth:
const shouldEnforcePluginGatewayAuth = (pathContext) => {
return shouldEnforceGatewayAuthForPluginPath(
getActivePluginRegistry() ?? params.pluginRegistry, pathContext
);
};After applying the patch:
- PUT → 405 ✓
- POST (no auth) → 401 ✓
Impact and severity
- Affected: All users of plugin-based channel webhooks (BlueBubbles confirmed; potentially WhatsApp, Signal, Google Chat, or any plugin using
registerWebhookTargetWithPluginRoute). - Severity: High — completely blocks inbound message delivery via webhooks.
- Frequency: 100% reproducible on every fresh gateway start in v2026.3.13.
- Consequence: Channels appear healthy in
openclaw status --deepbut cannot receive any inbound webhook messages. Silent message loss with no error surfaced to the user.
Additional information
- Workaround: Patch
gateway-cli-Ol-vpIk7.jsas described above (2 edits). The patch is overwritten onopenclaw update. - Suggested fix: Either (a) have
createGatewayPluginRequestHandlerread fromgetActivePluginRegistry()on each request (as in the patch), or (b) haveregisterPluginHttpRouteaccept an explicitregistryparameter from the caller (the channel system knows which registry it was started with), or (c) preventloadOpenClawPluginsfrom creating new registry objects when called for schema-only purposes. - The internal hooks system already handles this pattern correctly —
triggerInternalHookusesSymbol.for("openclaw.__openclaw_internal_hook_handlers__")as a process-global singleton, avoiding the same class of desynchronization. - Note:
gateway-cli-CuZs0RlJ.jsalso exists as a duplicate bundle but is NOT the one loaded at runtime. The active bundle isgateway-cli-Ol-vpIk7.js(loaded via dynamic import inprogram-context-KfI9f4T2.js).