April 15, 2026 • 版本: 2026.4.11

Firecrawl API Key SecretRef 在配置状态已解析的情况下仍返回 401 Unauthorized

使用 1Password SecretRefs 配置 Firecrawl webFetch.apiKey 时,尽管网关配置显示密钥已解析且已掩码,但请求仍然失败并返回 401 Unauthorized

🔍 症状

主要错误表现

web_fetch 请求通过 Firecrawl 插件路由,且 API 密钥使用 1Password SecretRef 时,尽管网关显示密钥已正确解析,但操作仍会因认证错误而失败。

shell

终端 1:验证配置解析状态

$ openclaw config get plugins.entries.firecrawl.config.webFetch.apiKey –verbose

sourceConfig: “op://openclaw/Firecrawl API key/credential” resolved: “••••••••••••••••” resolvedAt: “2026-04-15T10:23:41Z” status: “resolved”

shell

终端 2:执行 web_fetch 请求

$ openclaw tools web-fetch “https://example.com

Error: Firecrawl API error (401): Unauthorized: Invalid token at FirecrawlProvider.fetch (firecrawl-provider.ts:147) at WebFetchTool.execute (web-fetch-tool.ts:89) at Gateway.handleToolCall (gateway.ts:204)

配置检查差异

运行时配置视图显示该字段为 resolvedmasked,这表明物化层接受了 SecretRef。然而,Firecrawl 请求路径似乎接收到以下之一:

  • 未解析的原始 `op://...` 字符串
  • 过时的或不正确的配置快照
  • 已解析但未传播到提供程序实例的值

诊断 CLI 命令

shell

验证插件注册和运行时状态

openclaw plugins list –verbose | grep -A5 firecrawl

检查提供程序实际使用的 API 密钥

openclaw debug provider firecrawl –show-config

启用密钥解析的跟踪日志

OPENCLAW_LOG_LEVEL=trace openclaw gateway start 2>&1 | grep -i “firecrawl|secretref|apiKey”

环境上下文

  • OpenClaw 版本: 2026.4.11
  • 操作系统: Ubuntu(Linux 内核 5.15+)
  • 运行时模式: 网关本地模式
  • 密钥提供商: 1Password CLI(`op://` 方案)
  • 网关配置: JSON 配置文件

🧠 根因分析

架构故障点

该 bug 源于网关配置解析层与 Firecrawl 提供程序请求执行层之间的配置快照隔离失败

┌─────────────────────────────────────────────────────────────────────┐ │ GATEWAY PROCESS │ ├─────────────────────────────────────────────────────────────────────┤ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ Config Loader │───▶│ Secret Resolver │───▶│ Runtime Config │ │ │ │ (JSON/YAML) │ │ (1Password CLI) │ │ Snapshot │ │ │ └─────────────────┘ └─────────────────┘ └────────┬────────┘ │ │ │ │ │ │ snapshot │ │ │ copied │ │ ▼ │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │ WebFetchTool │───▶│ FirecrawlProv │───▶│ Provider Init │ │ │ │ (web-fetch) │ │ Instance │ │ (ctor/snapshot)│ │ │ └─────────────────┘ └─────────────────┘ └────────┬────────┘ │ │ │ │ │ │ uses │ │ ▼ │ │ ┌─────────────────┐ │ │ │ Firecrawl REST │ │ │ │ API Call │ │ │ └─────────────────┘ │ └─────────────────────────────────────────────────────────────────────┘

故障序列

  1. 配置加载阶段: 网关加载包含 `plugins.entries.firecrawl.config.webFetch.apiKey: "op://openclaw/Firecrawl API key/credential"` 的 JSON 配置。
  2. 密钥解析阶段: SecretResolver 服务处理 1Password SecretRef 并检索明文令牌。运行时配置视图正确显示为已解析和已掩码。
  3. 提供程序初始化阶段(BUG):FirecrawlProvider 被实例化时,它接收到的是过时的配置快照,而不是解析后的配置对象。提供程序的构造函数捕获了:
    // firecrawl-provider.ts - 构造函数(有 bug)
    constructor(config: FirecrawlConfig) {
      // 在初始化时捕获配置快照
      this.apiKey = config.webFetch.apiKey;
      this.baseUrl = config.webFetch.baseUrl;
      // ...
    }
  4. 请求执行阶段: 当调用 web_fetch 时,提供程序使用 this.apiKey,其中仍包含原始的 SecretRef 字符串 "op://openclaw/Firecrawl API key/credential",因为快照是在密钥解析完成之前拍摄的。
  5. Firecrawl API 拒绝: Firecrawl API 接收到字面量 op://... 字符串作为 API 密钥,导致 401 Unauthorized 错误。

代码路径分析

根本原因表现在 src/plugins/firecrawl/firecrawl-provider.ts 中的初始化顺序:

// 有问题的初始化顺序
class FirecrawlProvider {
  private apiKey: string;
  private baseUrl: string;

  // 在网关启动期间调用,接收预解析配置
  constructor(config: FirecrawlConfig) {
    this.apiKey = config.webFetch.apiKey;  // ← 接收 "op://..." 字符串
    this.baseUrl = config.webFetch.baseUrl;
  }

  // 此方法在请求时调用,但使用的是捕获的值
  async fetch(url: string): Promise<FetchResult> {
    const headers = {
      'Authorization': `Bearer ${this.apiKey}`,  // ← 发送未解析的引用
      'Content-Type': 'application/json'
    };
    // ...
  }
}

与正常工作明文路径的对比

使用明文配置时,不需要密钥解析:

{
  "plugins": {
    "entries": {
      "firecrawl": {
        "config": {
          "webFetch": {
            "apiKey": "fc-actual-token-plaintext"  // ← 直接赋值
          }
        }
      }
    }
  }
}

提供程序直接接收并存储实际令牌,绕过有问题的快照机制。

相关架构模式

此故障与 SecretRef 运行时物化 bug #28359 具有相同的特征,其中在不同生命周期阶段拍摄的配置快照包含不一致的解析状态。Firecrawl 提供程序的构造函数在初始化时捕获状态,但密钥解析对于此特定配置路径发生在初始化之后。

🛠️ 逐步修复

选项 1:延迟解析(推荐)

修改 Firecrawl 提供程序,在请求时而非构造时执行密钥解析。

文件: src/plugins/firecrawl/firecrawl-provider.ts

typescript // 修复前(有 bug) class FirecrawlProvider { private apiKey: string;

constructor(config: FirecrawlConfig) { this.apiKey = config.webFetch.apiKey; // 在初始化时捕获 }

async fetch(url: string): Promise { const response = await fetch(this.apiEndpoint, { headers: { ‘Authorization’: Bearer ${this.apiKey} } }); } }

// 修复后(已修复) import { SecretResolver } from ‘@openclaw/core/secrets’;

class FirecrawlProvider { private config: FirecrawlConfig; private secretResolver: SecretResolver;

constructor(config: FirecrawlConfig, secretResolver: SecretResolver) { this.config = config; this.secretResolver = secretResolver; // 注入解析器 }

async fetch(url: string): Promise { // 在请求时解析,而非构造时 const resolvedApiKey = await this.secretResolver.resolve( this.config.webFetch.apiKey );

const response = await fetch(this.apiEndpoint, {
  headers: { 'Authorization': `Bearer ${resolvedApiKey}` }
});

} }

文件: src/plugins/firecrawl/plugin-registration.ts

typescript // 修复前 export function registerFirecrawlPlugin(container: PluginContainer) { container.registerSingleton(FirecrawlProvider, (config) => new FirecrawlProvider(config) ); }

// 修复后 export function registerFirecrawlPlugin(container: PluginContainer) { container.registerSingleton(FirecrawlProvider, (config, secretResolver) => new FirecrawlProvider(config, secretResolver) ); }

选项 2:解析后刷新提供程序

在网关生命周期中密钥解析完成后,强制刷新提供程序。

文件: src/gateway/boot.ts

typescript // 添加到网关初始化序列 async function initializeGateway(config: GatewayConfig) { // 阶段 1:加载和解析密钥 const resolvedConfig = await configResolver.resolveWithSecrets(config);

// 阶段 2:使用解析后的配置初始化提供程序 await providerRegistry.initialize(resolvedConfig.plugins);

// 阶段 3(修复):刷新所有提供程序实例以确保使用解析后的值 await providerRegistry.refreshAll();

// 阶段 4:启动网关 await gateway.start(); }

选项 3:环境变量变通方案(紧急缓解)

如果无法进行代码级修复,请使用环境变量注入 API 密钥:

步骤 1: 在您的环境中设置 API 密钥:

shell export FIRECRAWL_API_KEY=“your-actual-api-key-from-1password”

步骤 2: 更新配置以引用环境变量:

json { “plugins”: { “entries”: { “firecrawl”: { “enabled”: true, “config”: { “webFetch”: { “apiKey”: “${FIRECRAWL_API_KEY}”, “baseUrl”: “https://api.firecrawl.dev” } } } } } }

步骤 3: 验证环境变量可访问:

shell echo $FIRECRAWL_API_KEY

应输出:your-actual-api-key-from-1password

如果直接使用 1Password CLI:

export FIRECRAWL_API_KEY=$(op read “op://openclaw/Firecrawl API key/credential”)

步骤 4:验证修复有效

应用任何修复后,执行:

shell openclaw tools web-fetch “https://example.com

预期输出应为获取的 HTML 内容,而非 401 错误。

🧪 验证

修复前诊断

在尝试修复之前确认 bug 存在:

shell

1. 验证 SecretRef 已配置

openclaw config get plugins.entries.firecrawl.config.webFetch.apiKey

预期输出:

op://openclaw/Firecrawl API key/credential

2. 检查 1Password CLI 是否已认证

op vault list

预期输出:

Vaults in personal:

├── openclaw

└── …

3. 确认密钥存在且可访问

op item get “Firecrawl API key” –vault openclaw

预期:包含 credential 字段的项目详情

4. 测试 web_fetch(修复前应失败)

openclaw tools web-fetch “https://httpbin.org/headers" –provider firecrawl

修复前预期结果:

Error: Firecrawl API error (401): Unauthorized: Invalid token

修复后预期结果:

{“headers”: {“Host”: “httpbin.org”, …}}

修复后验证步骤

步骤 1:验证密钥解析

shell openclaw config get plugins.entries.firecrawl.config.webFetch.apiKey –verbose

预期:

{

“sourceConfig”: “op://openclaw/Firecrawl API key/credential”,

“resolved”: “••••••••••••••••”,

“status”: “resolved”,

“resolvedAt”: “2026-04-15T10:23:41Z”

}

步骤 2:验证提供程序初始化

shell openclaw debug provider firecrawl –show-config

预期:apiKey 字段应显示 “••••••••••••••••"(掩码)

不应是原始 “op://…” 字符串

步骤 3:验证请求执行

shell

使用简单端点测试,该端点回显 Authorization 头

openclaw tools web-fetch “https://httpbin.org/headers" –provider firecrawl

预期:应返回包含 Host 头的 JSON

不应出现 401 错误

步骤 4:验证 Firecrawl API 交互(调试模式)

shell OPENCLAW_LOG_LEVEL=debug openclaw tools web-fetch “https://example.com” –provider firecrawl 2>&1 | grep -E “(firecrawl|Firecrawl|Authorization|Authorization: Bearer)”

预期:应显示正在发送的已解析 Bearer 令牌

Authorization 头中不应显示 “op://…”

步骤 5:自动化测试套件

如果可用,运行 Firecrawl 插件测试套件:

shell openclaw test –plugin firecrawl –secretref-mode

预期:所有测试通过,包括 SecretRef 场景

退出码验证

shell

成功情况

openclaw tools web-fetch “https://example.com” –provider firecrawl echo “Exit code: $?” # 应为 0

失败情况(修复前)

openclaw tools web-fetch “https://example.com” –provider firecrawl echo “Exit code: $?” # 应为非零(通常为 1)

⚠️ 常见陷阱

环境特定陷阱

  • 1Password CLI 未认证:
    在使用 SecretRef 之前,确保 1Password CLI 已登录:
    # 检查认证状态
    op account list
    

    如果未认证,请登录:

    op signin

    验证对保管库的访问:

    op vault list

  • 保管库访问权限:
    1Password 账户必须能够访问 SecretRef 中引用的保管库:
    # 验证保管库权限
    op account get
    

    确保保管库 “openclaw” 可访问

    op vault get openclaw

  • 服务账户 vs 个人账户:
    如果以服务方式运行网关,确保服务账户具有 1Password 访问权限,而不仅仅是您的个人会话:
    # 对于 systemd 服务,通过环境或 systemd-run 设置 1Password 会话
    # 避免依赖个人 1Password 会话令牌

配置陷阱

  • 配置中的双重解析:
    如果您已将 FIRECRAWL_API_KEY 设置为环境变量,同时也在配置中引用它,确保配置使用 ${FIRECRAWL_API_KEY} 语法,而非 $FIRECRAWL_API_KEY
    # 错误 - 将按字面量传递
    "apiKey": "$FIRECRAWL_API_KEY"
    

    正确 - 将被解析

    “apiKey”: “${FIRECRAWL_API_KEY}"

  • SecretRef 中的空白字符:
    1Password SecretRef 不能有前导/尾随空白字符:
    # 错误 - 可能导致解析失败
    "apiKey": "op://openclaw/Firecrawl API key/credential "
    

    正确

    “apiKey”: “op://openclaw/Firecrawl API key/credential”

  • 项目名称中的特殊字符:
    如果 1Password 项目名称包含特殊字符,请对其进行 URL 编码:
    # 对于名为 "Firecrawl API (Dev & Prod)" 的项目
    "apiKey": "op://openclaw/Firecrawl%20API%20(Dev%20%26%20Prod)/credential"

运行时陷阱

  • 需要重启网关:
    对 SecretRef 的更改需要重启网关才能生效,即使网关热重载其他配置更改:
    # 更改 SecretRef 后必须执行
    openclaw gateway restart
    

    以下操作不够:

    openclaw config reload # 仅重载非密钥配置

  • 提供程序单例行为:
    由于单例提供程序模式,在密钥解析之前缓存的提供程序实例将继续使用过时值,直到网关完全重启:
    # 验证没有过时实例
    openclaw debug provider firecrawl --status
    

    应显示:“Initialized: true”, “Config Snapshot: fresh”

  • Docker 卷挂载注意事项:
    如果在 Docker 中运行,确保正确挂载 1Password socket/凭证:
    # 错误 - 凭证在容器中不可用
    docker run openclaw:latest
    

    正确 - 挂载 1Password socket

    docker run -v openclaw_config:/app/config -e OP_SESSION=… openclaw:latest

macOS 特定问题

  • Keychain 访问提示:
    1Password CLI 在非交互式运行时可能会提示 Keychain 访问。在 CI/CD 环境中使用 op signin --account 配合服务账户。
  • SSH Agent 转发:
    如果 1Password 凭证通过 SSH 转发,请确保 OP_SESSION 环境变量也被转发。

Windows 特定问题

  • 路径分隔符:
    SecretRef 路径在 Windows 上也使用正斜杠。不要转换为反斜杠。
  • WSL2 环境变量:
    如果在 WSL2 中运行 OpenClaw 而 1Password 在 Windows 上,请设置 WSL2 中的 1Password CLI 并在 WSL2 中单独进行身份验证,与 Windows 主机分开。

🔗 相关错误

主要相关问题

  • #28359 - SecretRef 运行时物化不一致
    历史问题,描述了 SecretRef 值如何在不同的解析状态被捕获在配置快照中。Firecrawl bug 是这一更广泛模式的具体表现。

症状相关的错误

  • 401 Unauthorized: Invalid token
    通用认证失败。在此上下文中,由字面量 op://... 字符串作为 Bearer 令牌发送引起。
  • SecretRef resolution failed: Item not found
    表示 SecretRef 中指定的 1Password 项目或保管库不存在或不可访问。不同于 Firecrawl bug——此处解析本身失败。
  • Plugin initialization failed: Config snapshot mismatch
    内部 OpenClaw 错误,表示插件在初始化阶段接收到不一致的配置。如果 Firecrawl 提供程序初始化顺序被改变,可能会在网关启动期间出现。
  • Gateway config validation error: Invalid SecretRef format
    如果 SecretRef 不符合 op://vault/item/field 模式,则为模式验证失败。

错误码参考

错误码类别描述
SECRET_REF_001解析SecretRef 格式验证失败
SECRET_REF_002解析1Password CLI 未认证
SECRET_REF_003解析目标项目或保管库不可访问
SECRET_REF_004物化解析后的值未传播到消费者
PLUGIN_AUTH_401认证插件收到无效凭证
PLUGIN_INIT_001初始化提供程序使用过时的配置快照初始化

跨插件注意事项

相同的快照隔离模式影响其他接受以下内容的插件:

  • 通过 SecretRef 接收 API 密钥或令牌
  • 在网关启动期间初始化提供程序
  • 使用单例提供程序实例

已知可能受影响的插件:

  • plugins.entries.anthropic.config.apiKey
  • plugins.entries.google.config.credentials
  • plugins.entries.aws.config.accessKeyId
  • plugins.entries.azure.config.subscriptionKey

通过测试这些插件各自的 SecretRef 配置来验证它们是否遇到相同问题。

依据与来源

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