Skip to content

fix(line): use getRegistry callback in plugin HTTP handler to prevent stale-route 404#40224

Closed
joelnishanth wants to merge 3 commits intoopenclaw:mainfrom
joelnishanth:fix/line-webhook-route-404-issue-34631
Closed

fix(line): use getRegistry callback in plugin HTTP handler to prevent stale-route 404#40224
joelnishanth wants to merge 3 commits intoopenclaw:mainfrom
joelnishanth:fix/line-webhook-route-404-issue-34631

Conversation

@joelnishanth
Copy link
Copy Markdown
Contributor

Summary

  • Problem: After a plugin config change (e.g. enabling/disabling a plugin via the Control UI), loadOpenClawPlugins is called again with a different cache key, creating a new PluginRegistry and calling setActivePluginRegistry(newRegistry). The gateway HTTP handler (createGatewayPluginRequestHandler) still holds a reference to the original registry from startup. The LINE channel then re-registers /line/webhook on the new active registry, but all incoming requests are matched against the old (now empty) registry — returning 404 until a full gateway restart.
  • Why it matters: LINE webhooks silently fail — messages are dropped, no sessions created — whenever the plugin registry diverges from the HTTP handler's captured snapshot. Only a full gateway restart recovers the route.
  • What changed: createGatewayPluginRequestHandler now accepts a getRegistry callback (() => PluginRegistry) instead of a pinned registry object, and resolves the live registry on every request. shouldEnforceGatewayAuthForPluginPath in server-runtime-state.ts is updated to call requireActivePluginRegistry() inline as well.
  • What did NOT change: Route registration/unregistration logic in monitorLineProvider, registerPluginHttpRoute, channel lifecycle, auth enforcement behaviour.

Change Type (select all)

  • Bug fix

Scope (select all touched areas)

  • Gateway / orchestration
  • Integrations

Linked Issue/PR

User-visible / Behavior Changes

/line/webhook (and any other plugin-registered webhook path) remains reachable after plugin config edits without requiring a gateway restart.

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: macOS (LaunchAgent gateway)
  • Runtime/container: Node 22+
  • Integration/channel: LINE

Steps

  1. Start gateway with LINE channel configured.
  2. Enable or disable any plugin via openclaw config set plugins.X.enabled true/false (triggers loadOpenClawPlugins with a new cache key).
  3. curl -i http://127.0.0.1:18789/line/webhook

Expected

  • HTTP 200 (webhook handler responds)

Actual (before fix)

  • HTTP 404 until openclaw gateway restart

Evidence

  • Failing test/log before + passing after (WIP — tests to be added)

Human Verification (required)

  • Verified scenarios: tracing the registry lifecycle through loadOpenClawPluginssetActivePluginRegistrycreateGatewayPluginRequestHandler in code review; confirmed that the captured registry reference goes stale on any cache-key change.
  • Edge cases checked: cache-hit path (same key) — no behaviour change; cache-miss path (config change) — handler now always reads the current active registry.
  • What you did not verify: live end-to-end on a real LINE bot (pending).

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Failure Recovery (if this breaks)

  • How to disable/revert: revert src/gateway/server/plugins-http.ts and src/gateway/server-runtime-state.ts to the captured-registry pattern.
  • Known bad symptoms: plugin HTTP routes not matched at all (would be caught immediately in smoke tests).

Risks and Mitigations

  • Risk: requireActivePluginRegistry() called on every HTTP request adds a negligible Map-lookup overhead.
    • Mitigation: The lookup is O(1) via a module-level singleton; no measurable latency impact.

Note: This is a resubmission of #34646 (auto-closed due to PR queue limit). No other PRs exist for this issue.

Joel Nishanth · offlyn.AI

@openclaw-barnacle openclaw-barnacle bot added gateway Gateway runtime size: S labels Mar 8, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 8, 2026

Greptile Summary

This PR fixes a stale-registry 404 bug where the gateway HTTP handler held a captured reference to the initial PluginRegistry object. After a plugin config change, loadOpenClawPlugins creates a fresh registry via setActivePluginRegistry, but the handler kept routing against the original (now empty) snapshot — causing all plugin-registered webhooks (e.g. /line/webhook) to return 404 until a gateway restart.

The fix is clean and correct:

  • createGatewayPluginRequestHandler now accepts a getRegistry: () => PluginRegistry callback instead of a pinned registry object, resolving the live registry on each request via the module-level requireActivePluginRegistry() singleton (an O(1) lookup).
  • shouldEnforceGatewayAuthForPluginPath in server-runtime-state.ts similarly calls requireActivePluginRegistry() inline on each auth check.
  • The regression test added in plugins-http.test.ts directly exercises the swap scenario and validates the fix.

The core change (callback instead of captured reference) is correct, well-tested, and solves a real production issue with no behavior regressions. One minor cleanup item: the now-unused pluginRegistry parameter can be removed from the createGatewayRuntimeState params type and its call sites.

Confidence Score: 4/5

  • Safe to merge — the fix is minimal, well-tested, and solves a real production issue with no behavior regressions.
  • The core change (callback instead of captured reference) is correct and the regression test validates the exact failure scenario. The only concern is the now-unused pluginRegistry parameter left on the createGatewayRuntimeState params type, which is misleading but causes no runtime error and can be cleaned up in a follow-up.
  • No critical files require special attention. One minor cleanup: the pluginRegistry parameter can be removed from src/gateway/server-runtime-state.ts and src/gateway/server.impl.ts in a follow-up.

Comments Outside Diff (1)

  1. src/gateway/server-runtime-state.ts, line 55 (link)

    pluginRegistry parameter is now unused and can be removed.

    After this change, params.pluginRegistry is no longer referenced anywhere in createGatewayRuntimeState's function body — both previous uses have been replaced with calls to requireActivePluginRegistry(). However, the field is still declared in the params type and callers like server.impl.ts still pass it, making the public API misleading by implying the caller-supplied registry is used.

    Consider removing the field from the params type:

    And remove pluginRegistry, from the corresponding argument in the call to createGatewayRuntimeState(...) in src/gateway/server.impl.ts (line 553).

Last reviewed commit: cc44617

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cc4461706a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// with a fresh registry object) are always visible without a gateway restart.
const handlePluginRequest = createGatewayPluginRequestHandler({
registry: params.pluginRegistry,
getRegistry: requireActivePluginRegistry,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the runtime-state registry instead of global plugin state

createGatewayRuntimeState now ignores params.pluginRegistry and always reads requireActivePluginRegistry(), which introduces a hidden global dependency and can serve stale routes in minimal test gateway mode. In startGatewayServer, minimalTestGateway explicitly bypasses loadGatewayPlugins and passes a fresh empty registry (src/gateway/server.impl.ts:429-431), so this change can pull in whatever registry was left in global plugin runtime state from earlier tests/process activity, making plugin route/auth behavior nondeterministic.

Useful? React with 👍 / 👎.

… stale-route 404

When a plugin config change triggers loadOpenClawPlugins with a different
cache key, setActivePluginRegistry replaces the active registry with a new
object. createGatewayPluginRequestHandler was capturing the original registry
at startup, so any routes re-registered by channels (e.g. /line/webhook) on
the new registry were invisible to the HTTP handler — returning 404 until a
full gateway restart.

Fix: accept a getRegistry callback instead of a pinned registry reference so
the handler always resolves routes from the current active registry. Also
update the shouldEnforcePluginGatewayAuth call in server-runtime-state.ts
to use requireActivePluginRegistry() inline for the same reason.

Adds a regression test (stale-registry swap) that fails without the fix and
passes with it.

Closes openclaw#34631
@joelnishanth joelnishanth force-pushed the fix/line-webhook-route-404-issue-34631 branch from cc44617 to a14a537 Compare March 10, 2026 21:57
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a14a53753a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

* change (which replaces the active registry via setActivePluginRegistry)
* are always visible to the HTTP handler without a gateway restart.
*/
getRegistry: () => PluginRegistry;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update remaining handler call sites for getRegistry

Making getRegistry required here leaves two existing invocations in src/gateway/server/plugins-http.test.ts still passing registry: ...; when those tests execute, the returned handler reaches getRegistry() with undefined and throws before the route/auth assertions run. This means the commit introduces immediate breakage for the plugin HTTP test coverage path (and corresponding type-checking jobs) until all callers are migrated to getRegistry: () => registry.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d6f203616c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// with a fresh registry object) are always visible without a gateway restart.
const handlePluginRequest = createGatewayPluginRequestHandler({
registry: params.pluginRegistry,
getRegistry: requireActivePluginRegistry,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use provided plugin registry when building runtime handlers

createGatewayRuntimeState now ignores params.pluginRegistry and always reads requireActivePluginRegistry(), which introduces a process-global dependency into request routing. In startGatewayServer minimal gateway mode intentionally skips loadGatewayPlugins and passes an explicit empty registry (src/gateway/server.impl.ts:470-472,616), so these handlers can instead read whatever registry was last written globally by other startup/tests, making plugin route matching and gateway-auth decisions nondeterministic in that mode.

Useful? React with 👍 / 👎.

@masatohoshino
Copy link
Copy Markdown
Contributor

Superseded by #45150, which addresses the review feedback from this PR:

  1. Global dependency removed (getter injection instead)
  2. All call sites migrated
  3. Backward compatible (registry: param still works)

Leaving this PR for maintainer to close at their discretion.

@joelnishanth
Copy link
Copy Markdown
Contributor Author

Closing as stale. Will revisit if the issue resurfaces.

Joel Nishanth · offlyn.AI

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gateway Gateway runtime size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Title: LINE webhook route intermittently returns 404 until gateway restart (2026.3.2)

2 participants