openclaw - ✅(Solved) Fix isGatewayMessageChannel intermittently rejects valid third-party channel plugins (openclaw-weixin, qqbot) [2 pull requests, 1 comments, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#61358Fetched 2026-04-08 02:59:30
View on GitHub
Comments
1
Participants
1
Timeline
8
Reactions
0
Participants
Timeline (top)
referenced ×5cross-referenced ×2closed ×1

Error Message

The error is intermittent — it starts occurring without any config change and stops on its own. The failure window can last from minutes to hours.

Root Cause

The channel validation chain

  1. Gateway's agent handler validates request.channel via isGatewayMessageChannel() (gateway-cli-CWpalJNJ.js)
  2. isGatewayMessageChannel()listGatewayMessageChannels()listDeliverableMessageChannels()listPluginChannelIds()
  3. listPluginChannelIds()listRegisteredChannelPluginEntries()getActivePluginRegistry()?.channels

The problem: mutable registry vs pinned registry

The plugin system has a pinned channel surface mechanism designed to prevent channel plugins from being evicted:

  • getActivePluginChannelRegistry() → returns the pinned channel registry (stable after startup)
  • getActivePluginRegistry() → returns the mutable state.activeRegistry (can be overwritten at runtime)

listRegisteredChannelPluginEntries() in registry-DTO_OK4F.js uses getActivePluginRegistry() instead of getActivePluginChannelRegistry(). This means channel validation depends on a mutable global state that can change during runtime.

Why bundled channels always work

Bundled channels (telegram, discord, whatsapp, etc.) are in the hardcoded CHANNEL_IDS array. They pass validation via CHANNEL_IDS.includes(value) and never hit the plugin registry path.

Why third-party channels fail intermittently

Third-party channel plugins are loaded via the plugin system. Their presence in getActivePluginRegistry()?.channels depends on the runtime state of state.activeRegistry, which is a Symbol.for("openclaw.pluginRegistryState") global shared across the entire V8 isolate.

Startup pin gap

loadGatewayStartupPlugins() calls prepareGatewayPluginLoad() without beforePrimeRegistry, meaning the channel registry is not pinned during initial startup:

function loadGatewayStartupPlugins(params) {
    return prepareGatewayPluginLoad(params);  // no beforePrimeRegistry!
}

function reloadDeferredGatewayPlugins(params) {
    return prepareGatewayPluginLoad({
        ...params,
        beforePrimeRegistry: pinActivePluginChannelRegistry  // pinned here
    });
}

Only reloadDeferredGatewayPlugins pins the channel registry.

Fix Action

Fixed

PR fix notes

PR #61361: fix: use pinned channel registry for channel plugin validation

Description (problem / solution / changelog)

Summary

Fixes #61358

in src/channels/registry.ts reads from the mutable getActivePluginRegistry(), which can temporarily return an empty channel list during plugin reload cycles. This causes sessions_spawn to intermittently fail with unknown channel: <plugin-id> for all third-party channel plugins (openclaw-weixin, qqbot, etc.).

Root Cause

The plugin system already has a pinned channel surface mechanism (getActivePluginChannelRegistry()) designed to keep channel plugins stable after gateway startup. However, the channel validation chain does not use it:

isGatewayMessageChannel() → listGatewayMessageChannels() → listPluginChannelIds()
  → listRegisteredChannelPluginEntries() → getActivePluginRegistry()  ❌ mutable

Bundled channels (telegram, discord, etc.) are unaffected because they are in the hardcoded CHANNEL_IDS array and never hit the plugin registry path.

Fix

Change listRegisteredChannelPluginEntries() to prefer the pinned channel registry:

// Before
function listRegisteredChannelPluginEntries() {
  return getActivePluginRegistry()?.channels ?? [];
}

// After
function listRegisteredChannelPluginEntries() {
  const pinned = getActivePluginChannelRegistry();
  if (pinned && pinned.channels && pinned.channels.length > 0) {
    return pinned.channels;
  }
  return getActivePluginRegistry()?.channels ?? [];
}

Testing

  • Verified against OpenClaw 2026.4.2 source code
  • The pinned registry is populated during reloadDeferredGatewayPlugins() which runs after startup
  • Fallback to mutable registry ensures backward compatibility

Affected

  • Third-party channel plugins: openclaw-weixin, qqbot, and any non-bundled channel
  • No impact on bundled channels (telegram, discord, whatsapp, etc.)

Changed files

  • src/channels/registry.ts (modified, +18/-1)

PR #61366: fix: resolve intermittent 'unknown channel' errors for plugin channels in sessions_spawn

Description (problem / solution / changelog)

Summary

Fixes #61358 Closes #59181 (supersedes the suggested fixes there) Related: #48790, #55338

Problem

intermittently fails with for all third-party channel plugins (openclaw-weixin, qqbot, feishu, wecom, lightclawbot, etc.). This has two root causes:

Root Cause 1: Mutable registry swap (#61358, #48790)

reads from the mutable , which can be temporarily empty during plugin reload cycles. Bundled channels (telegram, discord, etc.) are unaffected because they are hardcoded in .

Root Cause 2: Split-brain registration (#59181)

Plugin channels may not be registered in the channel plugin registry at all, even though they are fully functional for message sending/receiving. The channel field in a spawn request comes from the parent session and is metadata-only — the subagent does not communicate through the channel directly.

Changes

1. (primary fix)

Skip strict channel validation for subagent spawn requests (). The channel is only used as metadata for routing announcement results, not for actual channel communication.

Non-spawn requests (chat.send, direct agent calls, etc.) still receive strict channel validation.

2. (defense-in-depth)

Use the pinned channel registry () as the primary source in , falling back to the mutable active registry. This prevents registry swap from affecting all callers of the channel plugin list.

Why this is safe

  • Subagent spawns communicate entirely through the internal spawn mechanism (task input → agent execution → announcement message)
  • The channel field is metadata for routing the announcement back to the requester session
  • If the channel is truly invalid, the announcement routing will gracefully degrade (no delivery), but the spawn itself succeeds
  • Non-spawn agent calls still validate channels strictly

Testing

  • Verified against OpenClaw 2026.4.2 source code
  • The combination is only set by in
  • Regular agent calls via chat.send always have or no lane

Changed files

  • src/channels/registry.ts (modified, +18/-1)
  • src/gateway/server-methods/agent.ts (modified, +12/-0)

Code Example

invalid agent params: unknown channel: openclaw-weixin

---

# Successful spawn
00:44:47 ⇄ res ✓ agent 63ms runId=announce:v1:agent:research-searcher:subagent:...

# First failure — 18 seconds later, same gateway process, no config changes
00:45:05 ⇄ res ✗ agent 1ms errorCode=INVALID_REQUEST errorMessage=invalid agent params: unknown channel: openclaw-weixin

# Also affects qqbot
19:02:51 ⇄ res ✗ agent 1ms errorCode=INVALID_REQUEST errorMessage=invalid agent params: unknown channel: qqbot

---

function loadGatewayStartupPlugins(params) {
    return prepareGatewayPluginLoad(params);  // no beforePrimeRegistry!
}

function reloadDeferredGatewayPlugins(params) {
    return prepareGatewayPluginLoad({
        ...params,
        beforePrimeRegistry: pinActivePluginChannelRegistry  // pinned here
    });
}

---

// Before:
function listRegisteredChannelPluginEntries() {
    return getActivePluginRegistry()?.channels ?? [];
}

// After:
function listRegisteredChannelPluginEntries() {
    return getActivePluginChannelRegistry()?.channels ?? getActivePluginRegistry()?.channels ?? [];
}

---

function loadGatewayStartupPlugins(params) {
    return prepareGatewayPluginLoad({
        ...params,
        beforePrimeRegistry: pinActivePluginChannelRegistry
    });
}
RAW_BUFFERClick to expand / collapse

GitHub Issue: isGatewayMessageChannel intermittently rejects valid third-party channel plugins

目标仓库: openclaw/openclaw 标题: isGatewayMessageChannel intermittently rejects valid third-party channel plugins (openclaw-weixin, qqbot)


Bug: isGatewayMessageChannel intermittently rejects valid third-party channel plugins

Version: OpenClaw 2026.4.2 (d74a122) Severity: High — blocks sessions_spawn from third-party channel sessions Channels affected: openclaw-weixin, qqbot (likely all non-bundled channel plugins)

Symptoms

When a user sends a message via a third-party channel plugin (e.g. openclaw-weixin), sessions_spawn intermittently fails with:

invalid agent params: unknown channel: openclaw-weixin

The error is intermittent — it starts occurring without any config change and stops on its own. The failure window can last from minutes to hours.

Evidence from gateway logs

# Successful spawn
00:44:47 ⇄ res ✓ agent 63ms runId=announce:v1:agent:research-searcher:subagent:...

# First failure — 18 seconds later, same gateway process, no config changes
00:45:05 ⇄ res ✗ agent 1ms errorCode=INVALID_REQUEST errorMessage=invalid agent params: unknown channel: openclaw-weixin

# Also affects qqbot
19:02:51 ⇄ res ✗ agent 1ms errorCode=INVALID_REQUEST errorMessage=invalid agent params: unknown channel: qqbot

Root cause analysis

The channel validation chain

  1. Gateway's agent handler validates request.channel via isGatewayMessageChannel() (gateway-cli-CWpalJNJ.js)
  2. isGatewayMessageChannel()listGatewayMessageChannels()listDeliverableMessageChannels()listPluginChannelIds()
  3. listPluginChannelIds()listRegisteredChannelPluginEntries()getActivePluginRegistry()?.channels

The problem: mutable registry vs pinned registry

The plugin system has a pinned channel surface mechanism designed to prevent channel plugins from being evicted:

  • getActivePluginChannelRegistry() → returns the pinned channel registry (stable after startup)
  • getActivePluginRegistry() → returns the mutable state.activeRegistry (can be overwritten at runtime)

listRegisteredChannelPluginEntries() in registry-DTO_OK4F.js uses getActivePluginRegistry() instead of getActivePluginChannelRegistry(). This means channel validation depends on a mutable global state that can change during runtime.

Why bundled channels always work

Bundled channels (telegram, discord, whatsapp, etc.) are in the hardcoded CHANNEL_IDS array. They pass validation via CHANNEL_IDS.includes(value) and never hit the plugin registry path.

Why third-party channels fail intermittently

Third-party channel plugins are loaded via the plugin system. Their presence in getActivePluginRegistry()?.channels depends on the runtime state of state.activeRegistry, which is a Symbol.for("openclaw.pluginRegistryState") global shared across the entire V8 isolate.

Startup pin gap

loadGatewayStartupPlugins() calls prepareGatewayPluginLoad() without beforePrimeRegistry, meaning the channel registry is not pinned during initial startup:

function loadGatewayStartupPlugins(params) {
    return prepareGatewayPluginLoad(params);  // no beforePrimeRegistry!
}

function reloadDeferredGatewayPlugins(params) {
    return prepareGatewayPluginLoad({
        ...params,
        beforePrimeRegistry: pinActivePluginChannelRegistry  // pinned here
    });
}

Only reloadDeferredGatewayPlugins pins the channel registry.

Suggested fixes

Fix 1: Use pinned channel registry for channel validation (preferred)

In src/channels/registry.ts, change listRegisteredChannelPluginEntries() to use the pinned channel registry:

// Before:
function listRegisteredChannelPluginEntries() {
    return getActivePluginRegistry()?.channels ?? [];
}

// After:
function listRegisteredChannelPluginEntries() {
    return getActivePluginChannelRegistry()?.channels ?? getActivePluginRegistry()?.channels ?? [];
}

Fix 2: Pin channel registry during initial startup

In the gateway startup code, ensure loadGatewayStartupPlugins also pins the channel registry:

function loadGatewayStartupPlugins(params) {
    return prepareGatewayPluginLoad({
        ...params,
        beforePrimeRegistry: pinActivePluginChannelRegistry
    });
}

Both fixes should be applied together for defense in depth.

Reproduction

  1. Configure a third-party channel plugin (e.g. openclaw-weixin)
  2. Send a message from that channel to main agent
  3. Main agent spawns a subagent (depth 1) — usually succeeds
  4. Subagent spawns a child (depth 2+) — intermittently fails with unknown channel: <plugin-id>

Environment

  • OS: Ubuntu Linux 6.8.0-101-generic (x64)
  • Node: v22.22.2
  • OpenClaw: 2026.4.2 (d74a122) via pnpm global install
  • Affected plugins: openclaw-weixin, qqbot

extent analysis

TL;DR

The most likely fix is to use the pinned channel registry for channel validation and pin the channel registry during initial startup.

Guidance

  • Identify the listRegisteredChannelPluginEntries() function in src/channels/registry.ts and modify it to use the pinned channel registry.
  • Update the loadGatewayStartupPlugins function to pin the channel registry during initial startup by adding the beforePrimeRegistry parameter.
  • Apply both fixes together for defense in depth to prevent intermittent failures.
  • Verify the fix by sending messages from third-party channel plugins and checking that the main agent and subagents spawn successfully.

Example

// Modified listRegisteredChannelPluginEntries function
function listRegisteredChannelPluginEntries() {
    return getActivePluginChannelRegistry()?.channels?? getActivePluginRegistry()?.channels?? [];
}

// Modified loadGatewayStartupPlugins function
function loadGatewayStartupPlugins(params) {
    return prepareGatewayPluginLoad({
       ...params,
        beforePrimeRegistry: pinActivePluginChannelRegistry
    });
}

Notes

The provided fixes assume that the getActivePluginChannelRegistry() function returns the pinned channel registry and that the pinActivePluginChannelRegistry function correctly pins the channel registry. Additionally, the fixes may require further testing and verification to ensure they resolve the intermittent failures.

Recommendation

Apply both suggested fixes (using the pinned channel registry for channel validation and pinning the channel registry during initial startup) to resolve the intermittent failures and prevent unknown channel errors.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING