openclaw - ✅(Solved) Fix [Bug]: openclaw status loads ALL channel plugins regardless of enabled state [2 pull requests, 1 comments, 2 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#62522Fetched 2026-04-08 03:03:05
View on GitHub
Comments
1
Participants
2
Timeline
3
Reactions
0
Participants
Timeline (top)
cross-referenced ×2commented ×1

When running openclaw status (or openclaw doctor), the CLI hangs for several minutes because resolveChannelPluginIds() returns all plugins that have channel capability in the manifest, regardless of whether they are actually enabled in config.

This causes ensurePluginRegistryLoaded({ scope: 'channels' }) to load every channel-capable plugin including ones that are explicitly disabled (feishu, wechat, etc.), triggering heavy I/O in the plugin loading pipeline.

Root Cause

In src/plugins/channel-plugin-ids.ts, the resolveChannelPluginIds() function only filters by plugin.channels.length > 0 but does not check the effective activation/enabled state:

function resolveChannelPluginIds(params) {
  return loadPluginManifestRegistry({...})
    .plugins.filter((plugin) => plugin.channels.length > 0)
    .map((plugin) => plugin.id);  // ← returns ALL channel-capable plugins
}

Compare with resolveGatewayStartupPluginIds() which correctly uses resolveEffectivePluginActivationState() to check if each plugin is actually enabled.

PR fix notes

PR #62556: fix(plugins): avoid loading disabled channel plugins for status

Description (problem / solution / changelog)

Summary

  • Problem: openclaw status channel-scoped registry loads treated every channel-capable manifest as eligible, including plugins that were effectively disabled.
  • Why it matters: status/health flows could pull in heavy disabled channel plugins, causing slow or hanging CLI runs in constrained environments.
  • What changed: channel scope now filters plugin ids through effective activation state, preserves explicit empty scoped loads, and keeps channel-scoped runtime loads scoped even when the resulting id list is empty.
  • What did NOT change (scope boundary): this does not change gateway startup plugin selection, provider loading, or general scope: "all" registry behavior.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • 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

  • Closes #62522
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: resolveChannelPluginIds() returned every manifest with plugin.channels.length > 0 without checking effective activation state, and the scoped plugin loader path also collapsed onlyPluginIds: [] into undefined, which degraded empty channel scopes back into full plugin loads.
  • Missing detection / guardrail: there was no test asserting that channel scope excludes disabled plugins or that an explicit empty scoped load stays empty all the way through ensurePluginRegistryLoaded() and loadOpenClawPlugins().
  • Contributing context (if known): status/health CLI paths use scope: "channels", so the issue was amplified on slower filesystems and plugin trees with many disabled channel bundles.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/plugins/channel-plugin-ids.test.ts, src/cli/plugin-registry.test.ts
  • Scenario the test should lock in: channel scope should exclude disabled channel plugins, and configured-channels should preserve onlyPluginIds: [] instead of falling back to a full load.
  • Why this is the smallest reliable guardrail: the regression lives entirely in plugin id resolution and scoped registry loading semantics; these tests exercise the exact boundary without needing real plugin I/O.
  • Existing test that already covers this (if any): src/cli/plugin-registry.test.ts already covered the empty scoped load expectation once the loader semantics were fixed.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

  • openclaw status / channel-scoped CLI flows stop loading disabled channel plugins.
  • Empty channel scopes remain empty instead of degenerating into full plugin loads.

Diagram (if applicable)

Before:
[status/health command] -> [resolve all channel-capable manifests] -> [load disabled channel plugins too] -> [slow/hanging CLI]

After:
[status/health command] -> [resolve only effectively enabled channel plugins] -> [preserve empty scoped loads] -> [load only intended plugins]

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)
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: Linux
  • Runtime/container: Node 24 / local clean worktree
  • Model/provider: N/A
  • Integration/channel (if any): plugin registry / channel plugin loading
  • Relevant config (redacted): plugin entries with enabled and disabled channel plugins

Steps

  1. Configure a channel plugin as enabled and another channel plugin as disabled.
  2. Run the channel-scope registry resolution path.
  3. Run ensurePluginRegistryLoaded({ scope: "configured-channels" }) followed by ensurePluginRegistryLoaded({ scope: "channels" }).

Expected

  • Disabled channel plugins are excluded from channel scope.
  • Explicit empty channel scopes stay empty and do not trigger a full plugin load.

Actual

  • Before this patch, disabled channel plugins were included, and empty channel scopes were treated like no scope.

Evidence

Attach at least one:

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

Human Verification (required)

  • Verified scenarios: pnpm test src/cli/plugin-registry.test.ts src/plugins/channel-plugin-ids.test.ts; pnpm test src/plugins/loader.test.ts -t cache; codex review --base upstream/main
  • Edge cases checked: disabled channel plugin omitted from resolveChannelPluginIds(); explicit empty scoped loads remain empty through runtime registry loading; escalation from configured-channels to channels still reloads with the larger enabled set.
  • What you did not verify: full pnpm build is currently red on clean upstream/main due unrelated baseline errors in src/agents/subagent-control.ts and src/auto-reply/reply/commands-subagents.ts.

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.

Compatibility / Migration

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

Risks and Mitigations

  • Risk: a caller relying on channel scope to opportunistically load disabled channel plugins will now see a narrower registry.
    • Mitigation: that behavior was the bug; scope: "all" remains unchanged for callers that truly need a full registry.

Changed files

  • src/plugins/channel-plugin-ids.test.ts (modified, +36/-1)
  • src/plugins/channel-plugin-ids.ts (modified, +17/-1)
  • src/plugins/loader.test.ts (modified, +34/-0)
  • src/plugins/loader.ts (modified, +6/-3)
  • src/plugins/runtime/runtime-registry-loader.ts (modified, +3/-1)

PR #62559: fix(cli): preload configured channels for status paths

Description (problem / solution / changelog)

Summary

  • preload only configured channel plugins for routed status and health commands
  • keep non-status command paths on the existing all scope
  • update CLI bootstrap and routing tests to lock in the narrower preload scope

Why

status and health currently route through CLI bootstrap with scope: "channels", which loads every channel-capable plugin even when the command only needs configured channels. That widens plugin preload unnecessarily and can make these commands much slower in environments with expensive plugin I/O.

Closes #62522

Changed files

  • src/cli/command-bootstrap.test.ts (modified, +2/-2)
  • src/cli/plugin-registry-loader.test.ts (modified, +2/-2)
  • src/cli/plugin-registry-loader.ts (modified, +2/-2)
  • src/cli/program/preaction.test.ts (modified, +3/-1)
  • src/cli/route.test.ts (modified, +6/-2)
  • src/plugins/runtime/runtime-registry-loader.test.ts (modified, +11/-0)
  • src/plugins/runtime/runtime-registry-loader.ts (modified, +4/-0)

Code Example

function resolveChannelPluginIds(params) {
  return loadPluginManifestRegistry({...})
    .plugins.filter((plugin) => plugin.channels.length > 0)
    .map((plugin) => plugin.id);  // ← returns ALL channel-capable plugins
}
RAW_BUFFERClick to expand / collapse

Bug type

Performance / CLI hang

Summary

When running openclaw status (or openclaw doctor), the CLI hangs for several minutes because resolveChannelPluginIds() returns all plugins that have channel capability in the manifest, regardless of whether they are actually enabled in config.

This causes ensurePluginRegistryLoaded({ scope: 'channels' }) to load every channel-capable plugin including ones that are explicitly disabled (feishu, wechat, etc.), triggering heavy I/O in the plugin loading pipeline.

Root cause

In src/plugins/channel-plugin-ids.ts, the resolveChannelPluginIds() function only filters by plugin.channels.length > 0 but does not check the effective activation/enabled state:

function resolveChannelPluginIds(params) {
  return loadPluginManifestRegistry({...})
    .plugins.filter((plugin) => plugin.channels.length > 0)
    .map((plugin) => plugin.id);  // ← returns ALL channel-capable plugins
}

Compare with resolveGatewayStartupPluginIds() which correctly uses resolveEffectivePluginActivationState() to check if each plugin is actually enabled.

Impact

  • openclaw status hangs for several minutes (5+ minutes observed)
  • openclaw doctor times out
  • In proot environments, plugin file I/O is extremely slow, amplifying the problem
  • Channels that are disabled still get loaded and initialized during the status command

Environment

  • OpenClaw: 2026.4.5
  • Node: v24.13.0
  • OS: Android/Termux (Linux 6.17.0-PRoot-Distro)
  • Install: proot-distro Debian

Reproduction

  1. Disable feishu/微信 channels in config
  2. Run openclaw status
  3. Observe: CLI hangs while loading plugins that should be disabled
  4. strace shows: massive file stat/open operations for all channel-capable plugins (talk-voice, tavily, etc.)

Suggested fix

resolveChannelPluginIds() should call resolveEffectivePluginActivationState() for each plugin, mirroring the pattern used in resolveGatewayStartupPluginIds().

extent analysis

TL;DR

Modify the resolveChannelPluginIds() function to filter plugins based on their effective activation state using resolveEffectivePluginActivationState().

Guidance

  • Update the resolveChannelPluginIds() function to include a check for the effective activation state of each plugin, similar to resolveGatewayStartupPluginIds().
  • Verify the fix by running openclaw status and checking if the CLI hangs or if the disabled plugins are loaded.
  • Test the fix in different environments, including proot environments, to ensure the issue is resolved across various setups.
  • Consider adding logging or debugging statements to monitor the plugin loading process and identify any potential issues.

Example

function resolveChannelPluginIds(params) {
  return loadPluginManifestRegistry({...})
    .plugins.filter((plugin) => plugin.channels.length > 0 && resolveEffectivePluginActivationState(plugin))
    .map((plugin) => plugin.id);
}

Notes

The suggested fix assumes that the resolveEffectivePluginActivationState() function is correctly implemented and returns the effective activation state of a plugin. If this function is not correctly implemented, additional debugging may be required.

Recommendation

Apply the suggested workaround by modifying the resolveChannelPluginIds() function to include the effective activation state check, as this should resolve the performance issue and prevent the CLI from hanging.

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