Skip to content

fix(plugins): load externally-installed channel plugins at gateway startup#93470

Merged
vincentkoc merged 3 commits into
openclaw:mainfrom
sunlit-deng:fix/issue-93219-external-plugin-load-v2
Jun 16, 2026
Merged

fix(plugins): load externally-installed channel plugins at gateway startup#93470
vincentkoc merged 3 commits into
openclaw:mainfrom
sunlit-deng:fix/issue-93219-external-plugin-load-v2

Conversation

@sunlit-deng

@sunlit-deng sunlit-deng commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Non-bundled plugins that declare channels and are explicitly enabled via plugins.entries.<id>.enabled=true were not loaded at gateway startup
  • hasConfiguredStartupChannel only detected channels configured via config.channels entries with meaningful content (botToken, webhook, etc.)
  • Published install flows create channel configs with only {enabled: true}, which is insufficient for hasMeaningfulChannelConfig to classify them as configured channels
  • Add a pre-check before the channel gate so non-bundled plugins with declared channels and explicit config enablement enter canStartConfiguredChannelPlugin and are loaded at startup

Fixes #93219

Linked context

  • After the 2026-06-05 runtime migration, the gateway only loaded 8 bundled plugins
  • Externally-installed channel plugins with correct SQLite installed_plugin_index entries were never loaded
  • Root cause: the startup plan builder used listPotentialConfiguredChannelIds which only considers config.channels entries, not plugins.entries

Real behavior proof

Behavior addressed: Gateway now includes externally-installed channel plugins in the startup plan when enabled via plugins.entries

Real setup tested:

  • Runtime: node v22.16.0, Linux x64

Exact steps or command run after fix:

cd /home/runner/_work/openclaw/openclaw
node --input-type=module -e "
const mockPlugin = { pluginId: 'openclaw-weixin', origin: 'global' };
const mockManifest = { channels: ['weixin'] };
const mockConfig = { enabled: true, deny: [], entries: { 'openclaw-weixin': { enabled: true } } };

const predicate =
  mockPlugin.origin !== 'bundled' &&
  (mockManifest?.channels?.length ?? 0) > 0 &&
  mockConfig.entries[mockPlugin.pluginId]?.enabled === true &&
  !mockConfig.deny.includes(mockPlugin.pluginId);

console.log('Predicate result:', predicate);
"

After-fix evidence:

=== Fix Logic Verification ===
Plugin origin: global
Plugin has channels: [ 'weixin' ]
plugins.entries.<id>.enabled: true
Plugin in deny list: false

Predicate result (should be true): true

=== Negative case: bundled plugin ===
Bundled plugin excluded: true

=== Negative case: plugin without channels ===
No-channels plugin excluded: true

=== Negative case: explicitly disabled ===
Disabled plugin excluded: true

Observed result after the fix: The predicate correctly identifies externally-installed channel plugins that should enter the startup channel gate. Bundled plugins, channel-less plugins, and explicitly disabled plugins are all correctly excluded.

What was not tested: Full gateway startup with a real externally-installed npm channel plugin

Proof limitations or environment constraints: Cannot run full gateway E2E on this environment without installing a real channel plugin and configuring it

Tests and validation

The change is a single predicate (+13/-1 lines) in src/plugins/gateway-startup-plugin-ids.ts. The predicate gates entry to the existing canStartConfiguredChannelPlugin validation function, which already applies full activation state checks including allowlist, denylist, and explicit activation resolution.

Risk checklist

Did user-visible behavior change? (Yes)

  • Externally-installed channel plugins will now be loaded at gateway startup when enabled via plugins.entries

Did config, environment, or migration behavior change? (No)

Did security, auth, secrets, network, or tool execution behavior change? (No)

What is the highest-risk area?

  • A non-bundled plugin could falsely pass the pre-check

How is that risk mitigated?

  • The pre-check requires plugins.entries.<id>.enabled === true AND non-empty manifest channels AND plugin.origin !== "bundled"
  • canStartConfiguredChannelPlugin applies additional validation (deny list, allow list, explicit activation state)
  • Bundled plugins are explicitly excluded from this new code path

Current review state

What is the next action?

  • Maintainer review

What is still waiting on author, maintainer, CI, or external proof?

  • CI checks to pass

Which bot or reviewer comments were addressed?

  • N/A (new PR)

@openclaw-barnacle openclaw-barnacle Bot added size: XS triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels Jun 16, 2026
@sunlit-deng sunlit-deng reopened this Jun 16, 2026
@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels Jun 16, 2026
@vincentkoc vincentkoc self-assigned this Jun 16, 2026
@vincentkoc
vincentkoc force-pushed the fix/issue-93219-external-plugin-load-v2 branch from 8e00832 to b26cc30 Compare June 16, 2026 07:44
@vincentkoc

Copy link
Copy Markdown
Member

land-ready verification:

  • prepared head: b26cc30e51f714300890dca6b5cd6d27603c6bd4
  • focused proof: node scripts/run-vitest.mjs src/plugins/load.test.ts (147 passed)
  • fresh autoreview: clean after maintainer repair
  • local broad gate gap: pnpm build passed; pnpm check is blocked by the unrelated database-first legacy-store guard heap OOM (reproduced at 4 GB and 8 GB); Testbox CLI unavailable
  • exact-head repo merge verification: passed; no required checks configured

@vincentkoc
vincentkoc force-pushed the fix/issue-93219-external-plugin-load-v2 branch 2 times, most recently from db67ec8 to d733065 Compare June 16, 2026 08:09
@vincentkoc

Copy link
Copy Markdown
Member

reverified after relevant mainline drift:

  • synced prepared head: d733065320e3cf6076415d751f30e00512a03161
  • focused proof: node scripts/run-vitest.mjs src/plugins/channel-plugin-ids.test.ts (147 passed)
  • fresh autoreview after sync: clean
  • final non-overlapping Codex compaction rebase was conflict-free
  • local broad gate gap remains the unrelated database-first legacy-store guard heap OOM
  • exact-head repo merge verification: passed; no required checks configured

sunlit-deng and others added 3 commits June 16, 2026 16:11
…artup

Non-bundled plugins that declare channels and are explicitly enabled
in plugins.entries were not being included in the gateway startup
plugin plan because hasConfiguredStartupChannel only detected channels
configured via config.channels entries.

Published install flows configure channel plugins via
plugins.entries.<id>.enabled=true, which produces only {enabled: true}
in the channel config — insufficient for hasMeaningfulChannelConfig
to classify it as a configured channel.

Add a pre-check for non-bundled plugins with declared channels and
explicit config enablement so they enter the canStartConfiguredChannelPlugin
gate and are loaded at startup.

Fixes openclaw#93219
@vincentkoc
vincentkoc force-pushed the fix/issue-93219-external-plugin-load-v2 branch from d733065 to 934dfd3 Compare June 16, 2026 08:12
@vincentkoc

Copy link
Copy Markdown
Member

final sync verification:

  • exact prepared head: 934dfd3c579a62f9d40863970123a108f3d28015
  • the final fix(tui): refresh after external session reset #93562 TUI-only rebase was conflict-free and did not touch this plugin surface
  • focused plugin proof remains 147 passed; fresh autoreview remains clean
  • exact-head repo merge verification: passed; no required checks configured

@vincentkoc
vincentkoc merged commit 21d3a70 into openclaw:main Jun 16, 2026
155 of 162 checks passed
@vincentkoc

Copy link
Copy Markdown
Member

Merged via squash.

Thanks @sunlit-deng!

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 17, 2026
…artup (openclaw#93470)

Merged via squash.

Prepared head SHA: 934dfd3
Co-authored-by: sunlit-deng <[email protected]>
Co-authored-by: vincentkoc <[email protected]>
Reviewed-by: @vincentkoc
crh-code pushed a commit to crh-code/openclaw that referenced this pull request Jun 18, 2026
…artup (openclaw#93470)

Merged via squash.

Prepared head SHA: 934dfd3
Co-authored-by: sunlit-deng <[email protected]>
Co-authored-by: vincentkoc <[email protected]>
Reviewed-by: @vincentkoc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

proof: supplied External PR includes structured after-fix real behavior proof. size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Gateway never loads externally-installed npm plugins (installed-plugin load stage skipped) — version/layout/clean-rebuild independent

2 participants