Slack Plugin Fails to Load with SecretRef Tokens: PluginLoadFailureError in agents Commands
When Slack channel tokens are stored as SecretRef objects, the Slack plugin eagerly resolves them during registration, causing the CLI to crash on all agents/* commands with a fatal PluginLoadFailureError.
๐ Symptoms
Any CLI command under the openclaw agents namespace terminates immediately with a fatal plugin load error when Slack channel tokens are configured as SecretRef objects. The error occurs before the command logic executes, rendering the entire CLI unusable for agent management.
Primary Error Output
$ openclaw agents list
[plugins] slack failed during register from .../extensions/slack/index.js:
Error: channels.slack.accounts.default.botToken: unresolved SecretRef "file:local:/SLACK_BOT_TOKEN".
Resolve this command against an active gateway runtime snapshot before reading it.
[openclaw] Failed to start CLI: PluginLoadFailureError: plugin load failed: slack: ...
$
$ echo $?
1Additional Affected Commands
$ openclaw agents add myagent --workspace ~/.openclaw/workspace-myagent
# Same PluginLoadFailureError output, exit code 1
$ openclaw agents remove myagent
# Same PluginLoadFailureError output, exit code 1
$ openclaw agents status myagent
# Same PluginLoadFailureError output, exit code 1Configuration Pattern That Triggers the Bug
The following openclaw.json configuration segment triggers the failure:
json { “channels”: { “slack”: { “accounts”: { “default”: { “botToken”: { “source”: “file”, “provider”: “local”, “id”: “/SLACK_BOT_TOKEN” }, “appToken”: { “source”: “file”, “provider”: “local”, “id”: “/SLACK_APP_TOKEN” } } } } } }
Distinction from Gateway Behavior
The running gateway process is not affected by this issue. The gateway holds an in-memory snapshot of resolved secrets and processes Slack events correctly. Only the CLI (a separate process) fails because it reads raw configuration from openclaw.json without access to the runtime secret-resolution context.
๐ง Root Cause
Execution Call Chain
The crash results from a specific sequence of eager resolution during plugin registration:
- Command Execution Path:
command-execution-startup.jssetsloadPlugins: "always"for allagentssubcommands, forcing complete plugin initialization on every invocation. - Strict Error Handling:
runtime-registry-loader.jsruns CLI plugin loading withthrowOnLoadError: true, converting any plugin registration error into a fatal condition. - Route Registration Triggers Resolution:
extensions/slack/index.jscallsregisterSlackPluginHttpRoutes()during its register hook. - Eager Token Access:
registerSlackPluginHttpRoutes()invokesresolveSlackAccount({cfg, accountId})unconditionally, including for the default account. - SecretRef Throws Unresolved Error:
accounts.jsโresolveSlackBotToken(merged.botToken, โฆ)โnormalizeResolvedSecretInputString()detects the unresolvedSecretRefobject and throws the fatal error.
Architectural Inconsistency
The Slack plugin violates the deferred-resolution principle expected of channel plugins:
- Expected Behavior: Plugin registration should register routes and prepare handlers. Actual token resolution should occur when processing an inbound event (i.e., within the HTTP request handler), at which point the gateway runtime snapshot is available.
- Actual Behavior: Token resolution occurs synchronously during route registration, before any request can be processed.
Why the CLI Cannot Resolve Secrets
The CLI process is a standalone executable that:
- Reads
openclaw.jsondirectly from disk. - Has no active gateway runtime connection.
- Cannot access the gateway's secret-resolution snapshot.
- Fails when encountering any
SecretRefthat has not been resolved to a concrete string value.
Insufficient Workarounds
| Workaround | Failure Reason |
|---|---|
Set SLACK_BOT_TOKEN env var | accounts.js evaluates the SecretRef structure before checking env fallback; throws before env resolution occurs. |
Set channels.slack.enabled: false | registerSlackPluginHttpRoutes iterates DEFAULT_ACCOUNT_ID unconditionally, ignoring the enabled flag during registration. |
Remove “slack” from plugins.allow | The plugin is also triggered by the channels.slack configuration block; removing from allowlist alone is insufficient to prevent loading. |
OPENCLAW_DISABLE_BUNDLED_PLUGINS=1 | Invalidates the entire channels configuration, reporting channels.slack: unknown channel id. |
๐ ๏ธ Step-by-Step Fix
Option A: Temporary Workaround (Immediate Relief)
Replace SecretRef objects with dummy string values before running CLI commands, then restore the original configuration.
Before (openclaw.json):
"botToken": { "source": "file", "provider": "local", "id": "/SLACK_BOT_TOKEN" },
"appToken": { "source": "file", "provider": "local", "id": "/SLACK_APP_TOKEN" }After (temporary):
"botToken": "xoxb-placeholder-do-not-use",
"appToken": "xapp-placeholder-do-not-use"Execute the required CLI command:
openclaw agents add myagent --workspace ~/.openclaw/workspace-myagentRestore the original SecretRef objects in openclaw.json immediately after.
Option B: Configuration-Based Workaround
Create a separate openclaw.json for CLI-only operations that excludes Slack configuration entirely.
- Backup the current configuration:
cp ~/.openclaw/openclaw.json ~/.openclaw/openclaw.json.gateway- Create a CLI-specific configuration without channel definitions:
cat > ~/.openclaw/openclaw-cli.json << 'EOF'
{
"version": "2",
"workspace": {
"default": "~/.openclaw/workspace-default"
},
"plugins": {
"allow": ["core", "agents"]
}
}
EOF- Use the CLI-specific config via environment variable:
OPENCLAW_CONFIG=~/.openclaw/openclaw-cli.json openclaw agents listOption C: Code Fix (Permanent Solution)
Modify the Slack plugin’s register hook to defer token resolution.
File: extensions/slack/index.js
Before (current problematic code):
function registerSlackPluginHttpRoutes(cfg) {
// Eagerly resolve account during registration
const account = resolveSlackAccount({ cfg, accountId: DEFAULT_ACCOUNT_ID });
router.post('/events', async (req, res) => {
// Handle Slack events...
});
}After (deferred resolution):
function registerSlackPluginHttpRoutes(cfg) {
// Register route unconditionally; resolve lazily on each request
router.post('/events', async (req, res) => {
// Resolve account only when processing an actual event
const account = resolveSlackAccount({ cfg, accountId: DEFAULT_ACCOUNT_ID });
// Handle Slack events...
});
}Apply the same pattern to any other routes that access account tokens (/interactions, /commands, etc.).
๐งช Verification
Verifying the Workaround (Option A)
- Confirm the original configuration contains
SecretRefobjects:
$ grep -A5 '"botToken"' ~/.openclaw/openclaw.json
"botToken": {
"source": "file",
"provider": "local",
"id": "/SLACK_BOT_TOKEN"
},- Temporarily replace with placeholder strings and run the command:
$ sed -i '' 's/"botToken": { "source": "file", "provider": "local", "id": "\/SLACK_BOT_TOKEN" }/"botToken": "xoxb-placeholder"/' ~/.openclaw/openclaw.json
$ sed -i '' 's/"appToken": { "source": "file", "provider": "local", "id": "\/SLACK_APP_TOKEN" }/"appToken": "xapp-placeholder"/' ~/.openclaw/openclaw.json
$ openclaw agents list
[agent list output]
$ echo $?
0- Restore the original configuration:
$ cp ~/.openclaw/openclaw.json.gateway ~/.openclaw/openclaw.json
# Verify restoration
$ grep -A5 '"botToken"' ~/.openclaw/openclaw.json | head -6
"botToken": {
"source": "file",
"provider": "local",
"id": "/SLACK_BOT_TOKEN"
},Verifying the Permanent Fix (Option C)
After applying the code fix to extensions/slack/index.js:
- Rebuild or reinstall the OpenClaw CLI:
$ npm run build # or the appropriate build command for your installation
$ openclaw --version
openclaw/2026.4.9 darwin-x64 node-v22.8.0- Verify the agents command now succeeds with
SecretRefconfiguration intact:
$ openclaw agents list
[agent list output]
$ echo $?
0- Confirm the gateway still processes Slack events correctly (if running):
$ curl -X POST http://localhost:3000/slack/events \
-H "Content-Type: application/json" \
-d '{"type": "url_verification", "challenge": "test"}'
{"challenge": "test"}- Check for no residual errors in logs:
$ tail -n 50 ~/.openclaw/logs/gateway.log | grep -i "secret\|token\|resolve"
# No error messages should appearโ ๏ธ Common Pitfalls
Pitfall 1: Forgetting to Restore Configuration
After using the placeholder workaround, users frequently forget to restore the original SecretRef configuration. This causes the running gateway to reload invalid placeholder tokens on next restart.
Mitigation: Always backup before modification and use atomic operations:
# Atomic swap with backup
cp ~/.openclaw/openclaw.json ~/.openclaw/openclaw.json.backup && \
sed -i 's/placeholder/dummy/' ~/.openclaw/openclaw.json && \
openclaw agents list && \
mv ~/.openclaw/openclaw.json.backup ~/.openclaw/openclaw.jsonPitfall 2: Multiple CLI Configurations Causing Confusion
Users who create a separate CLI config may accidentally modify the gateway config while expecting CLI behavior, or vice versa.
Mitigation: Always use the explicit OPENCLAW_CONFIG environment variable and verify which config is active:
$ OPENCLAW_CONFIG=~/.openclaw/openclaw-cli.json openclaw config show | grep workspacePitfall 3: Assuming enabled: false Prevents Plugin Loading
The Slack plugin loads regardless of the enabled flag because the plugin is triggered by both the plugins.allow list and the channels.slack configuration block.
Mitigation: Do not rely on enabled: false to skip plugin loading in the CLI context.
Pitfall 4: Environment Variable Workarounds Insufficient
Setting SLACK_BOT_TOKEN as an environment variable does not bypass the SecretRef error because the plugin evaluates the SecretRef structure first, before checking environment fallbacks.
Mitigation: Use the placeholder workaround or the separate CLI config approach instead.
Pitfall 5: Docker/Container Environments
In containerized deployments, the file:local secret provider may not have access to the host filesystem paths specified in the SecretRef.
Mitigation: Mount the secrets directory into the container:
docker run --rm \
-v $HOME/.openclaw:/root/.openclaw \
-v /run/secrets:/run/secrets:ro \
openclaw agents listPitfall 6: Windows Path Separators
On Windows, the SecretRef ID uses forward slashes (/SLACK_BOT_TOKEN) which may conflict with Windows path parsing in some configurations.
Mitigation: Use Windows-compatible secret IDs or configure the file:local provider with appropriate path mapping.
๐ Related Errors
PluginLoadFailureErrorโ Generic plugin loading failure. In this context, triggered specifically by the Slack plugin's eager token resolution during registration. Exit code: 1.channels.slack.accounts.default.botToken: unresolved SecretRefโ Specific error thrown bynormalizeResolvedSecretInputString()when encountering aSecretRefobject that has not been resolved to a string value.channels.slack: unknown channel idโ Error encountered when bundled plugins are disabled butchannels.slackconfiguration exists. Indicates misconfiguration of theOPENCLAW_DISABLE_BUNDLED_PLUGINSenvironment variable.SecretRefresolution errors in other plugins โ Similar eager-resolution issues may exist in other channel plugins (e.g., Microsoft Teams, Discord) if they follow the same registration pattern as Slack.OPENCLAW_CONFIGnot found โ Error when the specified configuration file path does not exist. Verify the path is absolute or relative to the correct working directory.throwOnLoadError: truein non-CLI contexts โ Any code path that invokes plugin loading withthrowOnLoadError: truewill exhibit this same failure mode, not just the CLI. Review all call sites ofruntime-registry-loader.js.