Bug type
Behavior bug (incorrect output/state without crash)
Summary
openclaw message send fails with "unresolved SecretRef" when channels.discord.token is a file-based SecretRef. The resolution code at src/commands/message.ts:22 resolves the ref correctly, but the resolved config is discarded downstream: sendMessageDiscord (src/discord/send.outbound.ts:136) calls loadConfig() to load a fresh unresolved config from disk instead of using the resolved config threaded through the call chain.
Steps to reproduce
- Configure
channels.discord.token as a SecretRef in openclaw.json:
"token": { "source": "file", "provider": "filemain", "id": "/channels/discord/token" }
with the token value stored in the file provider's secrets file, channels.discord.enabled: true, no accounts key.
- Start (or leave running) the gateway.
- Run:
openclaw message send --channel discord --target "channel:XXXX" --message "test"
Reproduced 2026-03-03 on a clean v2026.3.2 install. 100% repro regardless of gateway state.
Expected behavior
The CLI resolves the SecretRef and delivers the message. The resolution code exists and is called.
Actual behavior
[plugins] plugins.allow is empty; discovered non-bundled plugins may auto-load: acpx ...
Error: channels.discord.token: unresolved SecretRef "file:filemain:/channels/discord/token".
Resolve this command against an active gateway runtime snapshot before reading it.
No [secrets] diagnostic lines are logged.
OpenClaw version
2026.3.2
Operating system
Debian Linux (6.12.63+deb13-amd64)
Install method
npm global
Logs, screenshots, and evidence
Config state at time of failure:
channels.discord.enabled: true
channels.discord.accounts: not present
channels.discord.token: SecretRef object
secrets.defaults.file: "filemain"
secrets.providers: ["filemain"]
Root cause
messageCommand() at src/commands/message.ts:22 calls resolveCommandSecretRefsViaGateway(), which resolves the SecretRef and returns a config with the plaintext token. This resolved config is passed through runMessageAction -> handleSendAction -> executeSendAction -> sendMessage -> deliverOutboundPayloads -> the Discord outbound adapter.
At the final step, the Discord outbound adapter (src/channels/plugins/outbound/discord.ts:99) calls sendMessageDiscord(target, text, opts) without passing cfg in opts. Then sendMessageDiscord (src/discord/send.outbound.ts:136) calls const cfg = loadConfig() to load a fresh config from disk. This fresh config has the unresolved SecretRef. resolveDiscordAccount -> resolveDiscordToken -> normalizeDiscordToken -> assertSecretInputResolved throws.
The resolved config from the resolution chain is discarded. The resolution works but its result is never used.
Impact and severity
- Affected:
openclaw message send with any channel whose send function calls loadConfig() internally instead of accepting a resolved config. Confirmed for Discord; other channels may have the same pattern.
- Severity: High - blocks message delivery entirely (actual token needed to authenticate).
- Frequency: 100% repro on the
message send + Discord direct adapter path when the token is a SecretRef. Other channels may have the same pattern but are not confirmed.
- Consequence: Interactive CLI runs show a terminal error. Unattended cron jobs and scripts exit with code 1, causing silent message loss. The gateway stays healthy throughout.
Additional information
Bug type
Behavior bug (incorrect output/state without crash)
Summary
openclaw message sendfails with "unresolved SecretRef" whenchannels.discord.tokenis a file-based SecretRef. The resolution code atsrc/commands/message.ts:22resolves the ref correctly, but the resolved config is discarded downstream:sendMessageDiscord(src/discord/send.outbound.ts:136) callsloadConfig()to load a fresh unresolved config from disk instead of using the resolved config threaded through the call chain.Steps to reproduce
channels.discord.tokenas a SecretRef inopenclaw.json:channels.discord.enabled: true, noaccountskey.openclaw message send --channel discord --target "channel:XXXX" --message "test"Reproduced 2026-03-03 on a clean v2026.3.2 install. 100% repro regardless of gateway state.
Expected behavior
The CLI resolves the SecretRef and delivers the message. The resolution code exists and is called.
Actual behavior
No
[secrets]diagnostic lines are logged.OpenClaw version
2026.3.2
Operating system
Debian Linux (6.12.63+deb13-amd64)
Install method
npm global
Logs, screenshots, and evidence
Config state at time of failure:
Root cause
messageCommand()atsrc/commands/message.ts:22callsresolveCommandSecretRefsViaGateway(), which resolves the SecretRef and returns a config with the plaintext token. This resolved config is passed throughrunMessageAction->handleSendAction->executeSendAction->sendMessage->deliverOutboundPayloads-> the Discord outbound adapter.At the final step, the Discord outbound adapter (
src/channels/plugins/outbound/discord.ts:99) callssendMessageDiscord(target, text, opts)without passingcfgin opts. ThensendMessageDiscord(src/discord/send.outbound.ts:136) callsconst cfg = loadConfig()to load a fresh config from disk. This fresh config has the unresolved SecretRef.resolveDiscordAccount->resolveDiscordToken->normalizeDiscordToken->assertSecretInputResolvedthrows.The resolved config from the resolution chain is discarded. The resolution works but its result is never used.
Impact and severity
openclaw message sendwith any channel whose send function callsloadConfig()internally instead of accepting a resolved config. Confirmed for Discord; other channels may have the same pattern.message send+ Discord direct adapter path when the token is a SecretRef. Other channels may have the same pattern but are not confirmed.Additional information
message.tsin v2026.3.2. The resolution works correctly - the bug is that the result is discarded downstream.openclaw status. PR fix(config): make status command resilient to unresolved SecretRefs #33213 fixesstatuswith a degraded-state fallback. That approach does not apply here, but the root cause here is also different (config not threaded through, not a resolution failure).cfgfrom the outbound adapter context through tosendMessageDiscord, and havesendMessageDiscordaccept an optionalcfgparameter (opts.cfg ?? loadConfig()). Same fix needed forsendMediaand potentially other channel send functions.loadConfig()instead of using a passed config).