openclaw - ✅(Solved) Fix Unconfigured channel plugins fail to load in setup-runtime mode (bundled setup-entry mismatch) [1 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#64433Fetched 2026-04-11 06:14:59
View on GitHub
Comments
1
Participants
2
Timeline
3
Reactions
0
Timeline (top)
commented ×1cross-referenced ×1subscribed ×1

Channel plugins that are enabled but not yet configured (no credentials set) fail to initialize at startup with:

[plugins] <id> missing register/activate export
[plugins] 1 plugin(s) failed to initialize (validation: <id>). Run 'openclaw plugins list' for details.

In my case this reproduces for slack because I have no SLACK_BOT_TOKEN set. discord and telegram, which are configured, load fine via the full-mode path.

Error Message

As a side issue, the diagnostic blames index.js in its message because record.source still points at candidate.source, even though the file that was actually loaded and failed validation was setupSource (setup-entry.js). That made the error quite confusing to trace. And openclaw doctor reports Errors: 1 under Plugins with ERROR slack: plugin export missing register/activate (dist/extensions/slack/index.js). Because of this, the first-time setup flow for slack is unreachable — you can't configure the plugin to make the error go away, and you can't make the error go away without configuring it.

  1. Point the validation diagnostic at the file that was actually loaded (setupSource when in setup-runtime / setup-only mode) instead of record.source, so the error message isn't misleading.

Root Cause

When a channel plugin is enabled but isChannelConfigured(...) returns false, the loader picks registrationMode = "setup-runtime" (dist/loader-BSIqIOsD.js:2562). In that mode, the loader opens manifestRecord.setupSource (the plugin's setup-entry.js) instead of index.js.

Every bundled channel plugin's setup-entry.js is built via defineBundledChannelSetupEntry() (dist/channel-entry-contract-DWlZ12A9.js:196), which returns:

{
  kind: "bundled-channel-setup-entry",
  loadSetupPlugin: () => ...,
  loadSetupSecrets: () => ..., // optional
}

But the loader's resolveSetupChannelRegistration() (dist/loader-BSIqIOsD.js:2104) expects a plugin property that is a plain object:

function resolveSetupChannelRegistration(moduleExport) {
    const resolved = moduleExport && typeof moduleExport === "object" && "default" in moduleExport ? moduleExport.default : moduleExport;
    if (!resolved || typeof resolved !== "object") return {};
    const setup = resolved;
    if (!setup.plugin || typeof setup.plugin !== "object") return {};
    return { plugin: setup.plugin };
}

There's no plugin field on the bundled setup-entry export — only loadSetupPlugin. So resolveSetupChannelRegistration returns {}, the setup-registration branch at loader-BSIqIOsD.js:2713 is skipped, and the code falls through to the generic resolvePluginModuleExport(mod) path at line 2729. That path runs on the setup-entry module (not index.js), finds no register/activate function, and fails at line 2780.

As a side issue, the diagnostic blames index.js in its message because record.source still points at candidate.source, even though the file that was actually loaded and failed validation was setupSource (setup-entry.js). That made the error quite confusing to trace.

This affects every channel plugin that uses defineBundledChannelSetupEntry. It only reproduces when the plugin is enabled and the channel is not yet configured — configured channels take the full path and never touch setup-entry.js. So in practice, the first unconfigured bundled channel a user enables is what surfaces the bug.

Fix Action

Fix / Workaround

I verified locally that slackSetupPlugin (returned by loadSetupPlugin()) is a valid plugin object with id: "slack", setupWizard, commands, configSchema, etc. With this patch applied, openclaw doctor reports Loaded: 57, Errors: 0 and the first-time slack setup flow becomes reachable.

PR fix notes

PR #64601: fix(plugins): load bundled setup entries in setup-runtime

Description (problem / solution / changelog)

Summary

  • teach setup-runtime channel loading to accept bundled setup-entry exports that expose loadSetupPlugin
  • keep the existing direct plugin setup export path unchanged
  • add a regression test for unconfigured channel loads using the bundled setup-entry contract

Testing

  • pnpm -C F:\openclaw-codex-64433 exec node scripts/run-vitest.mjs run src/plugins/loader.test.ts --maxWorkers=1

Fixes #64433

Changed files

  • src/agents/test-helpers/fast-bash-tools.ts (modified, +2/-0)
  • src/agents/test-helpers/fast-openclaw-tools.ts (modified, +9/-0)
  • src/plugins/loader.test.ts (modified, +118/-27)
  • src/plugins/loader.ts (modified, +44/-19)

Code Example

[plugins] <id> missing register/activate export
[plugins] 1 plugin(s) failed to initialize (validation: <id>). Run 'openclaw plugins list' for details.

---

{
  kind: "bundled-channel-setup-entry",
  loadSetupPlugin: () => ...,
  loadSetupSecrets: () => ..., // optional
}

---

function resolveSetupChannelRegistration(moduleExport) {
    const resolved = moduleExport && typeof moduleExport === "object" && "default" in moduleExport ? moduleExport.default : moduleExport;
    if (!resolved || typeof resolved !== "object") return {};
    const setup = resolved;
    if (!setup.plugin || typeof setup.plugin !== "object") return {};
    return { plugin: setup.plugin };
}

---

[plugins] slack missing register/activate export
[plugins] 1 plugin(s) failed to initialize (validation: slack). Run 'openclaw plugins list' for details.

---

function resolveSetupChannelRegistration(moduleExport) {
    const resolved = moduleExport && typeof moduleExport === "object" && "default" in moduleExport ? moduleExport.default : moduleExport;
    if (!resolved || typeof resolved !== "object") return {};
    const setup = resolved;
    if (setup.plugin && typeof setup.plugin === "object") return { plugin: setup.plugin };
    if (typeof setup.loadSetupPlugin === "function") {
        try {
            const loaded = setup.loadSetupPlugin();
            if (loaded && typeof loaded === "object") return { plugin: loaded };
        } catch {}
    }
    return {};
}
RAW_BUFFERClick to expand / collapse

Summary

Channel plugins that are enabled but not yet configured (no credentials set) fail to initialize at startup with:

[plugins] <id> missing register/activate export
[plugins] 1 plugin(s) failed to initialize (validation: <id>). Run 'openclaw plugins list' for details.

In my case this reproduces for slack because I have no SLACK_BOT_TOKEN set. discord and telegram, which are configured, load fine via the full-mode path.

Environment

  • openclaw 2026.4.9 (0512059)
  • Installed via npm i -g openclaw
  • Linux, Node 22

Root cause

When a channel plugin is enabled but isChannelConfigured(...) returns false, the loader picks registrationMode = "setup-runtime" (dist/loader-BSIqIOsD.js:2562). In that mode, the loader opens manifestRecord.setupSource (the plugin's setup-entry.js) instead of index.js.

Every bundled channel plugin's setup-entry.js is built via defineBundledChannelSetupEntry() (dist/channel-entry-contract-DWlZ12A9.js:196), which returns:

{
  kind: "bundled-channel-setup-entry",
  loadSetupPlugin: () => ...,
  loadSetupSecrets: () => ..., // optional
}

But the loader's resolveSetupChannelRegistration() (dist/loader-BSIqIOsD.js:2104) expects a plugin property that is a plain object:

function resolveSetupChannelRegistration(moduleExport) {
    const resolved = moduleExport && typeof moduleExport === "object" && "default" in moduleExport ? moduleExport.default : moduleExport;
    if (!resolved || typeof resolved !== "object") return {};
    const setup = resolved;
    if (!setup.plugin || typeof setup.plugin !== "object") return {};
    return { plugin: setup.plugin };
}

There's no plugin field on the bundled setup-entry export — only loadSetupPlugin. So resolveSetupChannelRegistration returns {}, the setup-registration branch at loader-BSIqIOsD.js:2713 is skipped, and the code falls through to the generic resolvePluginModuleExport(mod) path at line 2729. That path runs on the setup-entry module (not index.js), finds no register/activate function, and fails at line 2780.

As a side issue, the diagnostic blames index.js in its message because record.source still points at candidate.source, even though the file that was actually loaded and failed validation was setupSource (setup-entry.js). That made the error quite confusing to trace.

This affects every channel plugin that uses defineBundledChannelSetupEntry. It only reproduces when the plugin is enabled and the channel is not yet configured — configured channels take the full path and never touch setup-entry.js. So in practice, the first unconfigured bundled channel a user enables is what surfaces the bug.

Reproduction

  1. Start from a workspace where the slack plugin is enabled (default).
  2. Do not set SLACK_BOT_TOKEN / SLACK_APP_TOKEN / SLACK_USER_TOKEN.
  3. Run openclaw doctor.

Expected: plugin stays in setup-runtime mode cleanly, ready for first-time setup.

Actual:

[plugins] slack missing register/activate export
[plugins] 1 plugin(s) failed to initialize (validation: slack). Run 'openclaw plugins list' for details.

And openclaw doctor reports Errors: 1 under Plugins with ERROR slack: plugin export missing register/activate (dist/extensions/slack/index.js).

Because of this, the first-time setup flow for slack is unreachable — you can't configure the plugin to make the error go away, and you can't make the error go away without configuring it.

Suggested fix

Teach resolveSetupChannelRegistration to handle bundled setup entries by calling loadSetupPlugin():

function resolveSetupChannelRegistration(moduleExport) {
    const resolved = moduleExport && typeof moduleExport === "object" && "default" in moduleExport ? moduleExport.default : moduleExport;
    if (!resolved || typeof resolved !== "object") return {};
    const setup = resolved;
    if (setup.plugin && typeof setup.plugin === "object") return { plugin: setup.plugin };
    if (typeof setup.loadSetupPlugin === "function") {
        try {
            const loaded = setup.loadSetupPlugin();
            if (loaded && typeof loaded === "object") return { plugin: loaded };
        } catch {}
    }
    return {};
}

I verified locally that slackSetupPlugin (returned by loadSetupPlugin()) is a valid plugin object with id: "slack", setupWizard, commands, configSchema, etc. With this patch applied, openclaw doctor reports Loaded: 57, Errors: 0 and the first-time slack setup flow becomes reachable.

Two related follow-ups worth considering:

  1. Point the validation diagnostic at the file that was actually loaded (setupSource when in setup-runtime / setup-only mode) instead of record.source, so the error message isn't misleading.
  2. Add a regression test that enables a bundled channel plugin without credentials and asserts the loader completes without errors.

extent analysis

TL;DR

The issue can be fixed by modifying the resolveSetupChannelRegistration function to handle bundled setup entries by calling loadSetupPlugin().

Guidance

  • The root cause of the issue is that the resolveSetupChannelRegistration function does not handle bundled setup entries correctly, leading to a missing register/activate export error.
  • To fix this, the resolveSetupChannelRegistration function needs to be updated to call loadSetupPlugin() when dealing with bundled setup entries.
  • The updated function should return the loaded plugin object if it is valid, allowing the setup flow to proceed.
  • Additionally, the validation diagnostic should be updated to point to the correct file (setupSource) instead of record.source to avoid misleading error messages.

Example

function resolveSetupChannelRegistration(moduleExport) {
    const resolved = moduleExport && typeof moduleExport === "object" && "default" in moduleExport ? moduleExport.default : moduleExport;
    if (!resolved || typeof resolved !== "object") return {};
    const setup = resolved;
    if (setup.plugin && typeof setup.plugin === "object") return { plugin: setup.plugin };
    if (typeof setup.loadSetupPlugin === "function") {
        try {
            const loaded = setup.loadSetupPlugin();
            if (loaded && typeof loaded === "object") return { plugin: loaded };
        } catch {}
    }
    return {};
}

Notes

  • This fix assumes that the loadSetupPlugin() function returns a valid plugin object.
  • The updated function should be thoroughly tested to ensure it works correctly for all bundled channel plugins.
  • A regression test should be added to ensure that the loader completes without errors when a bundled channel plugin is enabled without credentials.

Recommendation

Apply the suggested fix to the resolveSetupChannelRegistration function to allow the setup flow to proceed for bundled channel plugins without credentials. This fix should be applied to ensure that the first-time setup flow for bundled channel plugins is reachable and functional.

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

openclaw - ✅(Solved) Fix Unconfigured channel plugins fail to load in setup-runtime mode (bundled setup-entry mismatch) [1 pull requests, 1 comments, 2 participants]