April 16, 2026 • 版本: 2026.4.9

Slack 插件使用 SecretRef 令牌加载失败:agents/* 命令中的 PluginLoadFailureError

当 Slack 频道令牌存储为 SecretRef 对象时,Slack 插件在注册期间会急切地解析它们,导致 CLI 在所有 agents/* 命令上崩溃,出现致命的 PluginLoadFailureError。

🔍 症状

当 Slack 频道令牌配置为 SecretRef 对象时,openclaw agents 命名空间下的任何 CLI 命令都会立即终止,并抛出致命的插件加载错误。该错误在命令逻辑执行之前发生,导致整个 CLI 无法用于代理管理。

主要错误输出

$ 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

其他受影响的命令

$ 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

会触发该缺陷的配置模式

以下 openclaw.json 配置段会触发该故障:

json { “channels”: { “slack”: { “accounts”: { “default”: { “botToken”: { “source”: “file”, “provider”: “local”, “id”: “/SLACK_BOT_TOKEN” }, “appToken”: { “source”: “file”, “provider”: “local”, “id”: “/SLACK_APP_TOKEN” } } } } } }

与网关行为的区别

正在运行的网关进程不受此问题影响。网关持有解析后密钥的内存快照,并正确处理 Slack 事件。只有 CLI(一个独立的进程)会失败,因为它直接从 openclaw.json 读取原始配置,而无法访问运行时密钥解析上下文。

🧠 根因分析

执行调用链

该崩溃是由插件注册期间特定序列的急切解析导致的:

  1. 命令执行路径command-execution-startup.js 为所有 agents 子命令设置 loadPlugins: "always",强制在每次调用时完成插件初始化。
  2. 严格错误处理runtime-registry-loader.js 使用 throwOnLoadError: true 运行 CLI 插件加载,将任何插件注册错误转换为致命条件。
  3. 路由注册触发解析extensions/slack/index.js 在其注册钩子中调用 registerSlackPluginHttpRoutes()
  4. 急切访问令牌registerSlackPluginHttpRoutes() 无条件调用 resolveSlackAccount({cfg, accountId}),包括默认账户。
  5. SecretRef 抛出未解析错误accounts.jsresolveSlackBotToken(merged.botToken, …)normalizeResolvedSecretInputString() 检测到未解析的 SecretRef 对象并抛出致命错误。

架构不一致

Slack 插件违反了通道插件应有的延迟解析原则:

  • 预期行为:插件注册应注册路由并准备处理程序。实际令牌解析应在处理入站事件时(即在 HTTP 请求处理程序内)进行,此时网关运行时快照可用。
  • 实际行为:令牌解析在路由注册期间同步发生,在任何请求被处理之前。

CLI 为何无法解析密钥

CLI 进程是一个独立可执行文件,它:

  1. 直接从磁盘读取 openclaw.json
  2. 没有活动的网关运行时连接。
  3. 无法访问网关的密钥解析快照。
  4. 在遇到任何尚未解析为具体字符串值的 SecretRef 时失败。

不充分的变通方案

变通方案失败原因
设置 SLACK_BOT_TOKEN 环境变量accounts.js 在检查环境回退之前先评估 SecretRef 结构;在此之前就抛出异常。
设置 channels.slack.enabled: falseregisterSlackPluginHttpRoutes 无条件遍历 DEFAULT_ACCOUNT_ID,在注册期间忽略 enabled 标志。
plugins.allow 中移除 “slack”插件也由 channels.slack 配置块触发;仅从允许列表中移除不足以防止加载。
OPENCLAW_DISABLE_BUNDLED_PLUGINS=1使整个 channels 配置失效,报告 channels.slack: unknown channel id

🛠️ 逐步修复

方案 A:临时变通方案(立即缓解)

在运行 CLI 命令之前,将 SecretRef 对象替换为虚拟字符串值,然后恢复原始配置。

之前(openclaw.json):

"botToken": { "source": "file", "provider": "local", "id": "/SLACK_BOT_TOKEN" },
"appToken": { "source": "file", "provider": "local", "id": "/SLACK_APP_TOKEN" }

之后(临时):

"botToken": "xoxb-placeholder-do-not-use",
"appToken": "xapp-placeholder-do-not-use"

执行所需的 CLI 命令:

openclaw agents add myagent --workspace ~/.openclaw/workspace-myagent

立即在 openclaw.json 中恢复原始的 SecretRef 对象。

方案 B:基于配置的变通方案

创建单独用于 CLI 操作的 openclaw.json,完全排除 Slack 配置。

  1. 备份当前配置:
cp ~/.openclaw/openclaw.json ~/.openclaw/openclaw.json.gateway
  1. 创建不含通道定义的 CLI 专用配置:
cat > ~/.openclaw/openclaw-cli.json << 'EOF'
{
  "version": "2",
  "workspace": {
    "default": "~/.openclaw/workspace-default"
  },
  "plugins": {
    "allow": ["core", "agents"]
  }
}
EOF
  1. 通过环境变量使用 CLI 专用配置:
OPENCLAW_CONFIG=~/.openclaw/openclaw-cli.json openclaw agents list

方案 C:代码修复(永久解决方案)

修改 Slack 插件的注册钩子以延迟令牌解析。

文件:extensions/slack/index.js

之前(当前有问题的代码):

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...
  });
}

之后(延迟解析):

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...
  });
}

将相同的模式应用于任何其他访问账户令牌的路由(/interactions/commands 等)。

🧪 验证

验证变通方案(方案 A)

  1. 确认原始配置包含 SecretRef 对象:
$ grep -A5 '"botToken"' ~/.openclaw/openclaw.json
        "botToken": {
          "source": "file",
          "provider": "local",
          "id": "/SLACK_BOT_TOKEN"
        },
  1. 临时替换为占位符字符串并运行命令:
$ 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. 恢复原始配置:
$ 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"
        },

验证永久修复(方案 C)

在将代码修复应用到 extensions/slack/index.js 后:

  1. 重新构建或重新安装 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. 验证 agents 命令现在在 SecretRef 配置完整的情况下成功执行:
$ openclaw agents list
[agent list output]
$ echo $?
0
  1. 确认网关仍然正确处理 Slack 事件(如果正在运行):
$ curl -X POST http://localhost:3000/slack/events \
  -H "Content-Type: application/json" \
  -d '{"type": "url_verification", "challenge": "test"}'
{"challenge": "test"}
  1. 检查日志中没有残留错误:
$ tail -n 50 ~/.openclaw/logs/gateway.log | grep -i "secret\|token\|resolve"
# No error messages should appear

⚠️ 常见陷阱

陷阱 1:忘记恢复配置

在使用占位符变通方案后,用户经常忘记恢复原始 SecretRef 配置。这会导致运行的网关在下次重启时重新加载无效的占位符令牌。

缓解措施:始终在修改前备份并使用原子操作:

# 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

陷阱 2:多个 CLI 配置导致混淆

创建单独 CLI 配置的用户可能会在期望 CLI 行为时意外修改网关配置,反之亦然。

缓解措施:始终使用显式的 OPENCLAW_CONFIG 环境变量并验证活动配置:

$ OPENCLAW_CONFIG=~/.openclaw/openclaw-cli.json openclaw config show | grep workspace

陷阱 3:假设 enabled: false 可以防止插件加载

无论 enabled 标志如何,Slack 插件都会加载,因为插件由 plugins.allow 列表和 channels.slack 配置块共同触发。

缓解措施:不要依赖 enabled: false 来跳过 CLI 环境中的插件加载。

陷阱 4:环境变量变通方案不足

SLACK_BOT_TOKEN 设置为环境变量无法绕过 SecretRef 错误,因为插件首先评估 SecretRef 结构,然后才检查环境回退。

缓解措施:使用占位符变通方案或单独的 CLI 配置方法。

陷阱 5:Docker/容器环境

在容器化部署中,file:local 密钥提供程序可能无法访问 SecretRef 中指定的主机文件系统路径。

缓解措施:将密钥目录挂载到容器中:

docker run --rm \
  -v $HOME/.openclaw:/root/.openclaw \
  -v /run/secrets:/run/secrets:ro \
  openclaw agents list

陷阱 6:Windows 路径分隔符

在 Windows 上,SecretRef ID 使用正斜杠(/SLACK_BOT_TOKEN),这在某些配置中可能与 Windows 路径解析冲突。

缓解措施:使用 Windows 兼容的密钥 ID 或使用适当的路径映射配置 file:local 提供程序。

🔗 相关错误

  • PluginLoadFailureError — 通用插件加载失败。在此上下文中,由 Slack 插件在注册期间急切解析令牌而特别触发。退出代码:1。
  • channels.slack.accounts.default.botToken: unresolved SecretRef — 当遇到尚未解析为字符串值的 SecretRef 对象时,由 normalizeResolvedSecretInputString() 抛出的特定错误。
  • channels.slack: unknown channel id — 当禁用捆绑插件但存在 channels.slack 配置时遇到的错误。表示 OPENCLAW_DISABLE_BUNDLED_PLUGINS 环境变量的配置错误。
  • SecretRef resolution errors in other plugins — 其他通道插件(如 Microsoft Teams、Discord)如果遵循与 Slack 相同的注册模式,可能存在类似的急切解析问题。
  • OPENCLAW_CONFIG not found — 当指定的配置文件路径不存在时出现的错误。验证路径是绝对路径还是相对于正确工作目录的相对路径。
  • throwOnLoadError: true in non-CLI contexts — 任何使用 throwOnLoadError: true 调用插件加载的代码路径都会表现出相同的失败模式,不仅限于 CLI。审查 runtime-registry-loader.js 的所有调用点。

依据与来源

本故障排除指南由 FixClaw 智能管线从社区讨论中自动合成。