Skip to content

Gateway: rebind plugin registry before channel lifecycle#49526

Open
odex21 wants to merge 5 commits intoopenclaw:mainfrom
odex21:codex/bluebubbles-webhook-rebind
Open

Gateway: rebind plugin registry before channel lifecycle#49526
odex21 wants to merge 5 commits intoopenclaw:mainfrom
odex21:codex/bluebubbles-webhook-rebind

Conversation

@odex21
Copy link
Copy Markdown

@odex21 odex21 commented Mar 18, 2026

Summary

Describe the problem and fix in 2–5 bullets:

  • Problem: changing channels.bluebubbles.* could hot-reload the BlueBubbles channel after plugin runtime state had drifted, so the restart path could lose the live webhook registration and BlueBubbles webhook deliveries would fall through to 404 Not Found.
  • Why it matters: BlueBubbles could log that its webhook was listening, while http://localhost:18789/bluebubbles-webhook?... still behaved as if the route was missing after channel restart / hot reload.
  • What changed: gateway channel lifecycle calls now resolve and rebind the current channel-lifecycle plugin runtime state before start/stop/logout work, and config schema reads use snapshot plugin registries instead of mutating the live registry/hook-runner state.
  • What did NOT change (scope boundary): this does not change BlueBubbles webhook URLs, passwords, payload parsing, or webhook auth semantics.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

User-visible / Behavior Changes

In the verified local BlueBubbles hot-reload path, the webhook stays reachable after a channels.bluebubbles.* config change and channel restart instead of falling through to 404 Not Found.

Security Impact (required)

  • New permissions/capabilities? (Yes/No) No
  • Secrets/tokens handling changed? (Yes/No) No
  • New/changed network calls? (Yes/No) No
  • Command/tool execution surface changed? (Yes/No) No
  • Data access scope changed? (Yes/No) No
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: local gateway build / restart
  • Model/provider: N/A
  • Integration/channel (if any): BlueBubbles
  • Relevant config (redacted): channels.bluebubbles.* with a local webhook URL at http://localhost:18789/bluebubbles-webhook?...

Steps

  1. Start the local gateway with BlueBubbles configured.
  2. Change channels.bluebubbles.* in the local config so the BlueBubbles channel hot-reloads / restarts.
  3. Replay a webhook request to the existing BlueBubbles webhook URL.

Expected

  • The webhook route remains reachable after the config-triggered BlueBubbles restart, so the request reaches the BlueBubbles webhook handler.

Actual

  • Before this fix, the same replay could return 404 Not Found after channel restart / hot reload.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Manual replay evidence from the current local verification:

  • Config edit triggered: config change detected; evaluating reload (channels.bluebubbles.allowFrom, channels.bluebubbles.accounts.default.allowFrom)
  • Gateway restart path triggered: restarting bluebubbles channel
  • BlueBubbles re-registered: BlueBubbles webhook listening on /bluebubbles-webhook
  • Replay result after hot reload: 400 invalid payload

That 400 invalid payload is intentional from an incomplete test payload and shows the request now reaches the BlueBubbles webhook handler instead of missing the route.

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: on a local macOS gateway run, edited channels.bluebubbles.allowFrom / channels.bluebubbles.accounts.default.allowFrom, observed the config watcher trigger a BlueBubbles restart, and replayed the same webhook URL after restart.
  • Edge cases checked: the replay after hot reload returned 400 invalid payload instead of 404 Not Found, confirming handler reachability after the config-triggered restart.
  • What you did not verify: a full end-to-end live BlueBubbles conversation round-trip from a real inbound message through final OpenClaw reply delivery.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps:

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: revert this PR.
  • Files/config to restore: src/gateway/server-channels.ts, src/gateway/server-plugin-runtime-state.ts, src/gateway/server.impl.ts, src/gateway/server-methods/config.ts, src/channels/plugins/index.ts, src/channels/plugins/registry.ts, src/gateway/server-channels.test.ts, src/gateway/server.config-patch.test.ts
  • Known bad symptoms reviewers should watch for: missing or duplicated plugin HTTP routes during channel restart / hot reload, or config schema reads mutating live plugin runtime state.

Risks and Mitigations

List only real risks for this PR. Add/remove entries as needed. If none, write None.

  • Risk: channel lifecycle runtime rebinding now tracks more live plugin runtime state than the original narrow fix, so unsupported plugin runtime mutation paths may still surface edge cases outside the BlueBubbles repro.
    • Mitigation: regression coverage now covers stale-registry startup, same-channel live registry upgrades, and snapshot-only config schema loads; manual verification also confirms the local BlueBubbles hot-reload path no longer returns 404.

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

greptile-apps bot commented Mar 18, 2026

Greptile Summary

This PR fixes a race condition in the BlueBubbles channel hot-reload path where plugin HTTP routes were being registered on a stale PluginRegistry object (one that the gateway HTTP server no longer owned), causing webhook requests to return 404 Not Found. The fix introduces a syncPluginRegistry() helper inside createChannelManager that calls setActivePluginRegistry with the manager's registry before each channel lifecycle entry point, and threads the live pluginRegistry from startGatewayServer into the manager.

Key changes:

  • server-channels.ts: Adds optional pluginRegistry to ChannelManagerOptions and a syncPluginRegistry() call at the top of startChannelInternal, stopChannel, startChannels, and markChannelLoggedOut.
  • server.impl.ts: Passes pluginRegistry to createChannelManager.
  • server-channels.test.ts: Adds a targeted regression test simulating registry drift before channel startup.

Concern — deferred plugin loading path:
createChannelManager is constructed at line 627 with the initial pluginRegistry snapshot (registry1). When deferredConfiguredChannelPluginIds is non-empty, a second loadGatewayPlugins call at line 991 creates a new full-runtime registry2 and sets it as globally active. Later, when startGatewaySidecars calls startChannels(), syncPluginRegistry() restores registry1 (the pre-deferred, setup-mode registry), potentially overwriting registry2 and causing channels to start from incomplete plugin definitions. Before this PR, no such restoration happened, so registry2 would have remained active. The regression test does not cover this deferred-loading scenario.

Confidence Score: 3/5

  • Safe to merge for standard (non-deferred) gateway configurations; the deferred plugin loading path may experience a behavioural regression and warrants verification.
  • The core fix is correct and well-tested for the primary hot-reload scenario. However, the channelManager captures pluginRegistry by value at construction time (line 627 in server.impl.ts), before the conditional deferred reload at line 991 that may replace pluginRegistry with a new full-runtime registry. syncPluginRegistry() would then restore the older pre-deferred registry during channel startup, which is a behavioural change vs. pre-PR (where no restoration happened). This reduces confidence until the deferred-loading code path is verified or the implementation is updated to resolve the registry lazily.
  • src/gateway/server.impl.ts — specifically the interaction between the createChannelManager call at line 627 and the deferred loadGatewayPlugins at line 991 when deferredConfiguredChannelPluginIds is non-empty.

Comments Outside Diff (1)

  1. src/gateway/server.impl.ts, line 627-633 (link)

    P1 Stale registry captured when deferred plugin loading is active

    createChannelManager is called at line 627 with the current pluginRegistry (let's call it registry1). When deferredConfiguredChannelPluginIds.length > 0, a second loadGatewayPlugins call at line 991 creates a new registry2 and sets it as the globally active registry via setActivePluginRegistry(registry2).

    Later, when startGatewaySidecars invokes startChannels(), the newly added syncPluginRegistry() inside the manager calls setActivePluginRegistry(registry1) — restoring the pre-deferred, setup-mode registry and overwriting registry2. This means listChannelPlugins() and getChannelPlugin() will resolve from registry1 (which was loaded with preferSetupRuntimeForChannelPlugins: true), not the full-runtime registry2. Channels may then start with incomplete plugin definitions.

    Before this PR, the global active registry would have been registry2 at the point startChannels ran, so channels would start from the full-runtime registry. This PR introduces a behavioral regression in the deferred-loading path.

    One option is to pass the channel manager a factory/resolver rather than a snapshot:

    // server.impl.ts — pass a resolver so the manager always uses the latest registry
    const channelManager = createChannelManager({
      loadConfig,
      channelLogs,
      channelRuntimeEnvs,
      resolveChannelRuntime: getChannelRuntime,
      resolvePluginRegistry: () => pluginRegistry,   // always reads the current binding
    });

    And in server-channels.ts:

    const syncPluginRegistry = () => {
      const resolved = opts.resolvePluginRegistry?.();
      if (resolved) {
        setActivePluginRegistry(resolved);
      }
    };

    This ensures syncPluginRegistry() always uses whatever pluginRegistry the outer scope currently holds, including the value reassigned after deferred loading.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/gateway/server.impl.ts
    Line: 627-633
    
    Comment:
    **Stale registry captured when deferred plugin loading is active**
    
    `createChannelManager` is called at line 627 with the current `pluginRegistry` (let's call it `registry1`). When `deferredConfiguredChannelPluginIds.length > 0`, a second `loadGatewayPlugins` call at line 991 creates a new `registry2` and sets it as the globally active registry via `setActivePluginRegistry(registry2)`.
    
    Later, when `startGatewaySidecars` invokes `startChannels()`, the newly added `syncPluginRegistry()` inside the manager calls `setActivePluginRegistry(registry1)` — restoring the pre-deferred, setup-mode registry and overwriting `registry2`. This means `listChannelPlugins()` and `getChannelPlugin()` will resolve from `registry1` (which was loaded with `preferSetupRuntimeForChannelPlugins: true`), not the full-runtime `registry2`. Channels may then start with incomplete plugin definitions.
    
    Before this PR, the global active registry would have been `registry2` at the point `startChannels` ran, so channels would start from the full-runtime registry. This PR introduces a behavioral regression in the deferred-loading path.
    
    One option is to pass the channel manager a factory/resolver rather than a snapshot:
    
    ```typescript
    // server.impl.ts — pass a resolver so the manager always uses the latest registry
    const channelManager = createChannelManager({
      loadConfig,
      channelLogs,
      channelRuntimeEnvs,
      resolveChannelRuntime: getChannelRuntime,
      resolvePluginRegistry: () => pluginRegistry,   // always reads the current binding
    });
    ```
    
    And in `server-channels.ts`:
    
    ```typescript
    const syncPluginRegistry = () => {
      const resolved = opts.resolvePluginRegistry?.();
      if (resolved) {
        setActivePluginRegistry(resolved);
      }
    };
    ```
    
    This ensures `syncPluginRegistry()` always uses whatever `pluginRegistry` the outer scope currently holds, including the value reassigned after deferred loading.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/gateway/server.impl.ts
Line: 627-633

Comment:
**Stale registry captured when deferred plugin loading is active**

`createChannelManager` is called at line 627 with the current `pluginRegistry` (let's call it `registry1`). When `deferredConfiguredChannelPluginIds.length > 0`, a second `loadGatewayPlugins` call at line 991 creates a new `registry2` and sets it as the globally active registry via `setActivePluginRegistry(registry2)`.

Later, when `startGatewaySidecars` invokes `startChannels()`, the newly added `syncPluginRegistry()` inside the manager calls `setActivePluginRegistry(registry1)` — restoring the pre-deferred, setup-mode registry and overwriting `registry2`. This means `listChannelPlugins()` and `getChannelPlugin()` will resolve from `registry1` (which was loaded with `preferSetupRuntimeForChannelPlugins: true`), not the full-runtime `registry2`. Channels may then start with incomplete plugin definitions.

Before this PR, the global active registry would have been `registry2` at the point `startChannels` ran, so channels would start from the full-runtime registry. This PR introduces a behavioral regression in the deferred-loading path.

One option is to pass the channel manager a factory/resolver rather than a snapshot:

```typescript
// server.impl.ts — pass a resolver so the manager always uses the latest registry
const channelManager = createChannelManager({
  loadConfig,
  channelLogs,
  channelRuntimeEnvs,
  resolveChannelRuntime: getChannelRuntime,
  resolvePluginRegistry: () => pluginRegistry,   // always reads the current binding
});
```

And in `server-channels.ts`:

```typescript
const syncPluginRegistry = () => {
  const resolved = opts.resolvePluginRegistry?.();
  if (resolved) {
    setActivePluginRegistry(resolved);
  }
};
```

This ensures `syncPluginRegistry()` always uses whatever `pluginRegistry` the outer scope currently holds, including the value reassigned after deferred loading.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/gateway/server-channels.ts
Line: 467-472

Comment:
**Redundant `syncPluginRegistry()` call in `startChannels`**

`startChannels` calls `syncPluginRegistry()` at the top, then immediately calls `startChannel(plugin.id)``startChannelInternal` → which also calls `syncPluginRegistry()` for each channel. This means `setActivePluginRegistry` is called `1 + N` times (where N is the number of channel plugins) for what should be a single rebind.

It's safe as-is since the rebinds are idempotent, but removing the top-level call in `startChannels` and relying solely on the rebind inside `startChannelInternal` would be cleaner:

```suggestion
  const startChannels = async () => {
    for (const plugin of listChannelPlugins()) {
      await startChannel(plugin.id);
    }
  };
```

Alternatively, keep the call here but add a comment explaining why it's needed separately from the one in `startChannelInternal` (e.g., to handle the case where there are no configured channels so `startChannelInternal` is never called).

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: "Test: simplify gatew..."

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: c10d6189b4

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@odex21 odex21 force-pushed the codex/bluebubbles-webhook-rebind branch from c10d618 to 2882f67 Compare March 18, 2026 06:23
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: 707ad0860c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@odex21
Copy link
Copy Markdown
Author

odex21 commented Mar 18, 2026

Quick scope note relative to #45150 and #45568:

So the three fixes sit at different points in the chain:

  • request dispatch reads from which registry,
  • registry replacement preserves which routes,
  • channel lifecycle writes routes onto which registry during restart.

This PR is specifically the third case.

@odex21 odex21 force-pushed the codex/bluebubbles-webhook-rebind branch from 707ad08 to 58d7afb Compare March 18, 2026 06:48
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: 58d7afb3a2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@andyzhang88888
Copy link
Copy Markdown

Review

Rebinding the plugin registry before channel lifecycle operations is a targeted fix. Changes to server-channels.ts, server.impl.ts, and server-methods/config.ts are well-scoped.

Good test coverage in server-channels.test.ts and server.config-patch.test.ts.

LGTM. Verify existing gateway startup/reload tests pass.

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: 4bb5500d2f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

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: b7d1b2d029

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@odex21 odex21 force-pushed the codex/bluebubbles-webhook-rebind branch from 55f09d9 to 8247c0a Compare March 18, 2026 09:08
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: 8247c0a0bd

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

}
}

return { registry: activeRegistry, cacheKey: activeCacheKey };
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Don't promote registries with additional channel IDs

If a running gateway lazily bootstraps an external channel plugin via src/infra/outbound/channel-resolution.ts, this helper now upgrades channel lifecycle calls to that newer registry just because it contains a superset of channel IDs. However startGatewayServer() still builds channelLogs and channelRuntimeEnvs once from the original registry (src/gateway/server.impl.ts:556-561), and startChannelInternal() indexes those frozen maps when starting a channel (src/gateway/server-channels.ts:338-345). A later hot-reload/manual restart of the newly added channel will therefore call its startAccount with runtime: undefined (and usually log: undefined), which can break startup until the whole gateway restarts.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks, I agree this is a real concern for dynamically introduced channel plugins.

That said, I do not think it is the right thing to expand this PR to solve. This patch is scoped to the reproducible bug where an already-configured BlueBubbles channel stops receiving webhooks after a channels.bluebubbles.* hot reload. Newly discovered channel plugins are a different lifecycle boundary: today channelLogs / channelRuntimeEnvs are startup-scoped, so bringing a brand new channel online still effectively requires a full gateway restart.

In other words, I read this as a broader gateway runtime contract issue rather than a regression in the verified BlueBubbles restart path this PR is fixing. I’m leaving that boundary explicit here instead of widening this bug-fix PR further.

@odex21 odex21 force-pushed the codex/bluebubbles-webhook-rebind branch from 8247c0a to 2368548 Compare March 19, 2026 06:52
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: 2368548429

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +252 to +253
cache: false,
activate: false,
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 Keep schema snapshots from mutating live plugin globals

Making schema reads non-activating here avoids replacing the active plugin registry, but it does not keep the other plugin globals stable. Snapshot loads still execute api.registerInteractiveHandler() and api.registerContextEngine() in full registration mode (src/plugins/registry.ts:934-971), and those APIs mutate process-wide registries without any activate:false guard. After a config.get or config.schema* request, a newly discovered extension can therefore start handling interactive callbacks or become available through resolveContextEngine() even though the gateway never activated that plugin's live registry or hook runner.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks, I agree this points at a broader plugin-loader side effect issue.

The change in this PR was intentionally narrower: keep config.get / config.schema* from replacing the live active registry and global hook runner while the gateway is serving channel traffic. That was directly tied to the restart-path regression this PR is fixing.

The remaining global side effects from snapshot loads (registerInteractiveHandler, registerContextEngine, etc.) look real, but they are not part of the BlueBubbles hot-reload repro or the local/manual verification for this PR. I think that cleanup belongs in a separate follow-up that can define the intended snapshot-registration contract more explicitly, rather than continuing to widen this bug-fix PR.

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: 7202ce4410

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +553 to +554
pluginRegistry = channelLifecyclePluginRuntimeState.registry;
pluginRegistryCacheKey = channelLifecyclePluginRuntimeState.cacheKey;
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 Refresh WS method handlers when promoting a live plugin registry

When resolvePluginRuntimeStateForChannels() upgrades pluginRegistry here, the gateway only swaps the registry object and cache key. The WebSocket dispatcher has already snapshotted gatewayMethods and extraHandlers from the startup registry at src/gateway/server.impl.ts:576-577 and src/gateway/server.impl.ts:1139-1147, so the next channel lifecycle call can leave the process in a mixed state: getChannelPlugin()/hook lookups use the promoted registry, but plugin gateway methods from that registry still hit the old handler table and return unknown method. A concrete case is a live upgrade to Matrix's full runtime, which registers matrix.verify.* in extensions/matrix/index.ts; after the promotion those verification RPCs stay unavailable until the whole gateway restarts.

Useful? React with 👍 / 👎.

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

Labels

gateway Gateway runtime size: L

Projects

None yet

2 participants