Bug
@openclaw/discord@2026.5.3 (shipped as openclaw@2026.5.3-1 on npm) still fails to start the Discord channel when channels.discord.token is configured as an env-backed SecretRef, despite issue #76371 being closed by merged PR #76449 ("fix(secretrefs): resolve external channel contracts").
Failure mode shifted from the explicit crash on 2026.5.2 (unresolved SecretRef "env:default:DISCORD_BOT_TOKEN") to a silent skip: the plugin loads, no error is logged, but the Discord channel never starts and shows error: not configured in openclaw channels status.
Reproduction (clean)
openclaw@2026.5.3-1 installed (npm view openclaw version → 2026.5.3-1); @openclaw/discord@2026.5.3 plugin installed at ~/.openclaw/npm/node_modules/@openclaw/discord/.
~/.openclaw/openclaw.json includes:
{
"secrets": { "providers": { "default": { "source": "env" } } },
"channels": {
"discord": {
"enabled": true,
"token": { "source": "env", "provider": "default", "id": "DISCORD_BOT_TOKEN" }
}
}
}
DISCORD_BOT_TOKEN is set in the gateway service's env (via ~/.openclaw/.env autoload).
- Restart the gateway:
launchctl kickstart -k gui/$UID/ai.openclaw.gateway.
Expected (per #76449)
Discord channel starts. Log shows channels/discord :: [default] starting provider (@<bot>).
Actual
- No
starting provider log line. Plugin is loaded (counted in 9 plugins: ...; discord; ...) but channel never starts.
openclaw channels status shows: Discord default: enabled, configured, secret unavailable in this command path, stopped, disconnected, token:config (unavailable), health:not-running, error:not configured
openclaw status --deep channels table: Discord │ ON │ WARN │ configured token unavailable in this command path
- Removing the
channels.discord.token block (env-fallback only) → channel comes up immediately. So the trigger is the SecretRef config exactly as documented.
Root cause
PR #76449 added an external-plugin secret-contract-api loader at src/secrets/channel-contract-api.ts. Its path resolver, resolvePluginContractApiPath(rootDir), only checks <rootDir>/secret-contract-api.{js,mjs,cjs,ts,mts,cts} and <rootDir>/contract-api.{...}.
Real npm-published externalized channel plugins put compiled artifacts under <rootDir>/dist/ (per package.json openclaw.runtimeExtensions: ["./dist/index.js"]). For example, the actual sidecar in the installed Discord plugin lives at:
~/.openclaw/npm/node_modules/@openclaw/discord/dist/secret-contract-api.js
But the resolver only looks in:
~/.openclaw/npm/node_modules/@openclaw/discord/secret-contract-api.{js,mjs,cjs,ts,mts,cts} (none of these exist)
Returns null → loadExternalChannelSecretContractFromRecord returns undefined → collectChannelConfigAssignments skips Discord → no SecretRef assignment queued → runtime snapshot keeps channels.discord.token as the unresolved SecretRef object.
Downstream: selectDiscordRuntimeConfig returns the snapshot (same SecretRef value), resolveDiscordToken calls resolveSecretInputString({ mode: "inspect" }) on the still-unresolved SecretRef → returns configured_unavailable → account.token is "", account.tokenStatus is configured_unavailable → discordPlugin.config.isConfigured(account) returns false (extensions/discord/src/shared.ts:152: Boolean(account.token?.trim())) → gateway sets lastError: "not configured" and never calls gateway.startAccount.
The existing PR #76449 test fixture (src/secrets/channel-contract-api.external.test.ts) writes a flat-layout sidecar at <rootDir>/secret-contract-api.cjs, which masks the bug because no real-world npm package layout was exercised.
Why the failure mode is silent
Result: error:not configured only surfaces if you explicitly query openclaw channels status.
Fix
Make resolvePluginContractApiPath also check <rootDir>/dist/ for compiled npm-published externalized channel plugins. PR submitting now.
Workaround
Remove channels.discord.token from openclaw.json and rely on the env-fallback inside the Discord plugin (process.env.DISCORD_BOT_TOKEN). This bypasses the SecretRef path entirely, which is documented as supported per extensions/discord/src/doctor.ts and docs/channels/discord.md.
Environment
- OS: macOS 26.2 (arm64), Node 25.9.0
- OpenClaw: 2026.5.3-1 (npm global, LaunchAgent-managed gateway)
- Plugin:
@openclaw/discord@2026.5.3 installed at ~/.openclaw/npm/node_modules/@openclaw/discord/
- Build artifact verified on disk:
dist/secret-contract-api.js exists, root-level secret-contract-api.* does not.
Disclosure
This bug report and the accompanying PR were drafted with AI assistance (Claude Code, model claude-opus-4-7) using interactive code review of the installed plugin source and the cloned openclaw/openclaw main branch. The author has independently verified the on-disk paths and the production behavior, and has tested the fix locally against src/secrets/channel-contract-api.external.test.ts.
Bug
@openclaw/discord@2026.5.3(shipped asopenclaw@2026.5.3-1on npm) still fails to start the Discord channel whenchannels.discord.tokenis configured as an env-backed SecretRef, despite issue #76371 being closed by merged PR #76449 ("fix(secretrefs): resolve external channel contracts").Failure mode shifted from the explicit crash on 2026.5.2 (
unresolved SecretRef "env:default:DISCORD_BOT_TOKEN") to a silent skip: the plugin loads, no error is logged, but the Discord channel never starts and showserror: not configuredinopenclaw channels status.Reproduction (clean)
openclaw@2026.5.3-1installed (npm view openclaw version→2026.5.3-1);@openclaw/discord@2026.5.3plugin installed at~/.openclaw/npm/node_modules/@openclaw/discord/.~/.openclaw/openclaw.jsonincludes:{ "secrets": { "providers": { "default": { "source": "env" } } }, "channels": { "discord": { "enabled": true, "token": { "source": "env", "provider": "default", "id": "DISCORD_BOT_TOKEN" } } } }DISCORD_BOT_TOKENis set in the gateway service's env (via~/.openclaw/.envautoload).launchctl kickstart -k gui/$UID/ai.openclaw.gateway.Expected (per #76449)
Discord channel starts. Log shows
channels/discord :: [default] starting provider (@<bot>).Actual
starting providerlog line. Plugin is loaded (counted in9 plugins: ...; discord; ...) but channel never starts.openclaw channels statusshows:Discord default: enabled, configured, secret unavailable in this command path, stopped, disconnected, token:config (unavailable), health:not-running, error:not configuredopenclaw status --deepchannels table:Discord │ ON │ WARN │ configured token unavailable in this command pathchannels.discord.tokenblock (env-fallback only) → channel comes up immediately. So the trigger is the SecretRef config exactly as documented.Root cause
PR #76449 added an external-plugin secret-contract-api loader at
src/secrets/channel-contract-api.ts. Its path resolver,resolvePluginContractApiPath(rootDir), only checks<rootDir>/secret-contract-api.{js,mjs,cjs,ts,mts,cts}and<rootDir>/contract-api.{...}.Real npm-published externalized channel plugins put compiled artifacts under
<rootDir>/dist/(perpackage.jsonopenclaw.runtimeExtensions: ["./dist/index.js"]). For example, the actual sidecar in the installed Discord plugin lives at:But the resolver only looks in:
Returns
null→loadExternalChannelSecretContractFromRecordreturnsundefined→collectChannelConfigAssignmentsskips Discord → no SecretRef assignment queued → runtime snapshot keepschannels.discord.tokenas the unresolved SecretRef object.Downstream:
selectDiscordRuntimeConfigreturns the snapshot (same SecretRef value),resolveDiscordTokencallsresolveSecretInputString({ mode: "inspect" })on the still-unresolved SecretRef → returnsconfigured_unavailable→account.tokenis"",account.tokenStatusisconfigured_unavailable→discordPlugin.config.isConfigured(account)returnsfalse(extensions/discord/src/shared.ts:152: Boolean(account.token?.trim())) → gateway setslastError: "not configured"and never callsgateway.startAccount.The existing PR #76449 test fixture (
src/secrets/channel-contract-api.external.test.ts) writes a flat-layout sidecar at<rootDir>/secret-contract-api.cjs, which masks the bug because no real-world npm package layout was exercised.Why the failure mode is silent
normalizeDiscordTokenused the strict resolver which threw → loud crash.resolveDiscordTokenusesmode: "inspect"which gracefully returnsconfigured_unavailable. The framework path then bails onisConfigured === falsewithout logging anything channel-specific.Result:
error:not configuredonly surfaces if you explicitly queryopenclaw channels status.Fix
Make
resolvePluginContractApiPathalso check<rootDir>/dist/for compiled npm-published externalized channel plugins. PR submitting now.Workaround
Remove
channels.discord.tokenfromopenclaw.jsonand rely on the env-fallback inside the Discord plugin (process.env.DISCORD_BOT_TOKEN). This bypasses the SecretRef path entirely, which is documented as supported perextensions/discord/src/doctor.tsanddocs/channels/discord.md.Environment
@openclaw/discord@2026.5.3installed at~/.openclaw/npm/node_modules/@openclaw/discord/dist/secret-contract-api.jsexists, root-levelsecret-contract-api.*does not.Disclosure
This bug report and the accompanying PR were drafted with AI assistance (Claude Code, model
claude-opus-4-7) using interactive code review of the installed plugin source and the clonedopenclaw/openclawmainbranch. The author has independently verified the on-disk paths and the production behavior, and has tested the fix locally againstsrc/secrets/channel-contract-api.external.test.ts.