April 16, 2026 โ€ข Version: 2026.4.9

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 $?
1

Additional 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 1

Configuration 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:

  1. Command Execution Path: command-execution-startup.js sets loadPlugins: "always" for all agents subcommands, forcing complete plugin initialization on every invocation.
  2. Strict Error Handling: runtime-registry-loader.js runs CLI plugin loading with throwOnLoadError: true, converting any plugin registration error into a fatal condition.
  3. Route Registration Triggers Resolution: extensions/slack/index.js calls registerSlackPluginHttpRoutes() during its register hook.
  4. Eager Token Access: registerSlackPluginHttpRoutes() invokes resolveSlackAccount({cfg, accountId}) unconditionally, including for the default account.
  5. SecretRef Throws Unresolved Error: accounts.js โ†’ resolveSlackBotToken(merged.botToken, โ€ฆ) โ†’ normalizeResolvedSecretInputString() detects the unresolved SecretRef object 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:

  1. Reads openclaw.json directly from disk.
  2. Has no active gateway runtime connection.
  3. Cannot access the gateway's secret-resolution snapshot.
  4. Fails when encountering any SecretRef that has not been resolved to a concrete string value.

Insufficient Workarounds

WorkaroundFailure Reason
Set SLACK_BOT_TOKEN env varaccounts.js evaluates the SecretRef structure before checking env fallback; throws before env resolution occurs.
Set channels.slack.enabled: falseregisterSlackPluginHttpRoutes iterates DEFAULT_ACCOUNT_ID unconditionally, ignoring the enabled flag during registration.
Remove “slack” from plugins.allowThe plugin is also triggered by the channels.slack configuration block; removing from allowlist alone is insufficient to prevent loading.
OPENCLAW_DISABLE_BUNDLED_PLUGINS=1Invalidates 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-myagent

Restore 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.

  1. Backup the current configuration:
cp ~/.openclaw/openclaw.json ~/.openclaw/openclaw.json.gateway
  1. 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
  1. Use the CLI-specific config via environment variable:
OPENCLAW_CONFIG=~/.openclaw/openclaw-cli.json openclaw agents list

Option 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)

  1. Confirm the original configuration contains SecretRef objects:
$ grep -A5 '"botToken"' ~/.openclaw/openclaw.json
        "botToken": {
          "source": "file",
          "provider": "local",
          "id": "/SLACK_BOT_TOKEN"
        },
  1. 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
  1. 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:

  1. 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
  1. Verify the agents command now succeeds with SecretRef configuration intact:
$ openclaw agents list
[agent list output]
$ echo $?
0
  1. 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"}
  1. 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.json

Pitfall 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 workspace

Pitfall 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 list

Pitfall 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.

  • 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 by normalizeResolvedSecretInputString() when encountering a SecretRef object that has not been resolved to a string value.
  • channels.slack: unknown channel id โ€” Error encountered when bundled plugins are disabled but channels.slack configuration exists. Indicates misconfiguration of the OPENCLAW_DISABLE_BUNDLED_PLUGINS environment variable.
  • SecretRef resolution 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_CONFIG not found โ€” Error when the specified configuration file path does not exist. Verify the path is absolute or relative to the correct working directory.
  • throwOnLoadError: true in non-CLI contexts โ€” Any code path that invokes plugin loading with throwOnLoadError: true will exhibit this same failure mode, not just the CLI. Review all call sites of runtime-registry-loader.js.

Evidence & Sources

This troubleshooting guide was automatically synthesized by the FixClaw Intelligence Pipeline from community discussions.