April 22, 2026 • 版本: v2026.2.17

认证模式静默从OAuth切换至API密钥 - Auth Mode Silently Switched from OAuth to API Key

网关针对ChatGPT模型的认证模式意外从OAuth回退至API密钥模式,导致意外的API配额消耗和代理无响应。

🔍 症状

主要错误表现

问题通过级联故障序列表现出来:


# 初始警告(07:34 记录 7+ 次)
WARN  [gateway] Config invalid; doctor will run with best-effort config.

# 随后为静默认证模式切换(此转换无日志条目)

# 14:36 - 配额耗尽
ERROR [gateway] LLM request failed: OpenAI API error 429
{
  "error": {
    "type": "insufficient_quota",
    "message": "You exceeded your current quota, please ensure you have provided your own API key."
  }
}

# 14:43 - 回退耗尽
ERROR [gateway] Anthropic fallback failed: insufficient credits
{
  "error": {
    "type": "invalid_request_error",
    "message": "Your credit balance is too low"
  }
}

行为症状

  • 认证模式转换:用户报告其网关从 OAuth(ChatGPT Plus 订阅)切换到 API 密钥模式,无需任何手动配置更改。
  • 静默失败:没有认证模式实际转换的日志条目,仅从日志无法诊断。
  • 代理无响应:一旦 OpenAI 配额和 Anthropic 回退积分都耗尽,所有 LLM 操作完全失败。
  • Doctor 警告刷屏:"Config invalid" 消息连续出现 7+ 次,表明配置状态持续异常,doctor 反复尝试修复。

环境上下文

Gateway Version:  v2026.2.17
Model:           openai/gpt-5-chat-latest
Gateway Mode:    local
Operating System: macOS (darwin)
Timeline:        2026-02-28 07:34 - 14:43

🧠 根因分析

技术分析

根本原因源于配置状态损坏,触发了"doctor"恢复机制,该机制在配置无效时无条件回退到 API 密钥认证。

故障序列

  1. 配置失效:网关配置文件在约 07:34 变得无效或不可读取。
  2. Doctor 恢复触发doctor 子系统检测到配置无效并启动自动修复。
  3. 认证模式重置:在尽力恢复期间,doctor 写入了最小有效配置,由于恢复路径中 OAuth 令牌持久化不可用,默认设置为 auth_mode: api-key
  4. 静默转换:认证模式从 oauth 更改为 api-key,没有对应的日志条目,因为认证转换的日志记录被卡在已经失败的配置验证之后。
  5. 配额耗尽:没有配置有效的 API 密钥(或使用已耗尽的密钥),网关尝试的请求消耗了可用的配额,然后失败。

架构不一致

关键的架构缺陷在 config/doctor.go 中:

// BEFORE (buggy behavior)
func (d *Doctor) repairConfig() error {
    // Reads existing config to preserve settings
    cfg, err := d.loadConfig()
    if err != nil {
        // Config is invalid - start fresh
        cfg = &Config{}  // <-- PROBLEM: Creates empty config with defaults
    }
    
    // ... repair logic ...
    
    // Missing: Log the auth mode transition
    // Missing: Preserve OAuth tokens from previous session
    return d.saveConfig(cfg)
}

doctor 恢复路径未实现:

  • 在降级到 api-key 时记录认证模式转换
  • 在回退前尝试从安全存储恢复 OAuth 令牌
  • 在持久化前验证恢复的配置是否实际可用

OAuth 令牌持久化缺口

OAuth 令牌与主配置文件分开存储,通常在钥匙串或安全凭据存储中。在 doctor 恢复期间:

// The doctor saves a new config with auth_mode: api-key
// But it never checks: "Are OAuth tokens still available in keychain?"
// If yes, why are we switching to api-key mode?

🛠️ 逐步修复

临时解决方案(用户侧)

如果您立即遇到此问题:

# 1. Stop the gateway
openclaw gateway stop

# 2. Clear the corrupted config
rm -f ~/.openclaw/config.yaml

# 3. Restart the gateway (will prompt for fresh OAuth authentication)
openclaw gateway start

# 4. Verify auth mode is set to oauth
openclaw config get auth.mode
# Expected output: oauth

永久修复(需要代码更改)

修复 1:添加认证模式转换日志

config/doctor.go 中添加认证模式变更的日志记录:

// AFTER (fixed behavior)
func (d *Doctor) repairConfig() error {
    cfg, err := d.loadConfig()
    
    previousAuthMode := ""
    if err == nil {
        previousAuthMode = cfg.Auth.Mode
    }
    
    if err != nil {
        cfg = &Config{}
    }
    
    // ... repair logic ...
    
    // Log auth mode transition if it changed
    if previousAuthMode != "" && cfg.Auth.Mode != previousAuthMode {
        log.Info("[auth] mode transition detected",
            "from", previousAuthMode,
            "to", cfg.Auth.Mode,
            "reason", "config_repair")
    }
    
    return d.saveConfig(cfg)
}

修复 2:在回退前尝试恢复 OAuth 令牌

// AFTER (fixed behavior)
func (d *Doctor) attemptOAuthRecovery() (bool, error) {
    // Check if OAuth tokens exist in secure storage
    tokens, err := keychain.GetTokens("openclaw-oauth")
    if err != nil || tokens == nil {
        return false, nil  // No OAuth tokens available
    }
    
    // Tokens exist - restore OAuth mode instead of falling back to api-key
    cfg := &Config{
        Auth: AuthConfig{
            Mode:     "oauth",
            Provider: "openai",
        },
        OAuth: OAuthConfig{
            AccessToken:  tokens.AccessToken,
            RefreshToken: tokens.RefreshToken,
            ExpiresAt:    tokens.ExpiresAt,
        },
    }
    
    log.Info("[auth] restored OAuth session from keychain during config repair")
    return true, d.saveConfig(cfg)
}

修复 3:添加配置验证保护

gateway/main.go 启动序列中:

// AFTER (fixed behavior)
func startGateway() error {
    // Load and validate config before anything else
    cfg, err := config.Load()
    if err != nil {
        return fmt.Errorf("config load failed: %w", err)
    }
    
    if err := cfg.Validate(); err != nil {
        // Do NOT silently run doctor - halt and inform user
        return fmt.Errorf("config validation failed: %w. Run 'openclaw doctor --fix' to repair.", err)
    }
    
    // ... rest of startup ...
}

🧪 验证

验证修复

应用代码更改后,通过以下步骤验证修复:

# 1. Corrupt the config deliberately to test doctor recovery
echo "invalid: [yaml" > ~/.openclaw/config.yaml

# 2. Start the gateway
openclaw gateway start

# 3. Check logs for auth mode transition
grep -A5 "auth.*mode transition" ~/.openclaw/logs/gateway.log
# Expected output:
# INFO [auth] mode transition detected from=oauth to=api-key reason=config_repair

验证 OAuth 恢复功能

# 1. Clear config
rm -f ~/.openclaw/config.yaml

# 2. Manually set up OAuth tokens in keychain (simulate existing session)
openclaw auth store --provider openai --oauth-access-token "test_token" --oauth-refresh-token "refresh_test"

# 3. Start gateway with corrupted config
echo "invalid: yaml" > ~/.openclaw/config.yaml
openclaw gateway start

# 4. Verify OAuth mode was restored instead of falling back to api-key
openclaw config get auth.mode
# Expected output: oauth

# 5. Check restoration log
grep "restored OAuth session" ~/.openclaw/logs/gateway.log
# Expected output:
# INFO [auth] restored OAuth session from keychain during config repair

回归测试清单

  • 干净启动:全新 OAuth 认证创建有效配置
  • 配置损坏:Doctor 修复配置而不丢失 OAuth 模式
  • 日志完整性:每次认证模式变更都记录原因
  • 钥匙串持久化:OAuth 令牌在配置损坏后仍保留
  • 启动验证:网关在配置无效时快速失败并显示清晰错误

⚠️ 常见陷阱

环境特定陷阱

macOS (darwin)

  • 钥匙串权限:如果 OpenClaw 通过 Homebrew 安装,可能没有钥匙串访问权限。通过 System Preferences > Security & Privacy > Privacy > Keychain Access 授予。
  • 文件协调:macOS 可能会缓存配置文件读取。如果文件协调导致过时读取,请使用 csrutil 检查。
  • 路径展开:配置路径中的波浪号(~)在某些情况下可能无法正确展开。请始终使用 $HOME 或绝对路径。

Docker/容器化

  • 卷权限:如果配置从主机挂载,请确保 UID/GID 兼容性。OpenClaw 默认以 UID 1000 运行。
  • 钥匙串不可用:Docker 容器无法访问主机钥匙串。OAuth 令牌必须通过环境变量或 Docker 兼容的密钥存储传递。
  • 配置覆盖:多个 -v ~/.openclaw:/app/.openclaw 挂载可能导致竞争条件。使用单个卷挂载点。

Windows

  • 路径分隔符:Windows 上的配置路径使用反斜杠。PowerShell 可能意外转义这些字符。
  • 凭据管理器:Windows 使用凭据管理器 API 而不是钥匙串。确保 OpenClaw 有权访问 Manage credentials
  • WSL2 文件系统:如果在 WSL2 中运行 OpenClaw 并使用 Windows 挂载卷(/mnt/c),文件锁定行为可能异常。

用户配置错误

  • YAML 语法错误:常见错误包括:
    • 使用制表符而不是空格进行缩进
    • 键后缺少冒号
    • 包含特殊字符的字符串未加引号
  • 认证模式不匹配:设置 auth.mode: oauth 但未提供 OAuth 凭据将在令牌刷新后立即失败。
  • 过时令牌缓存:更改密码或撤销访问后,缓存的 OAuth 令牌将失效。必须通过 openclaw auth login 重新认证。
  • 多个配置文件:OpenClaw 从多个位置读取配置(./openclaw.yaml~/.openclaw/config.yaml/etc/openclaw/config.yaml)。冲突的配置可能导致静默失败。

开发特定问题

  • 模拟模式混淆:在开发期间,OPENCLAW_MOCK_AUTH=1 环境变量会绕过真实认证。确保生产环境中未设置此变量。
  • 测试夹具:集成测试可能写入 ~/.openclaw/test-config.yaml,如果测试未清理,可能会覆盖生产配置。

🔗 相关错误

直接相关错误

  • Config invalid; doctor will run with best-effort config.
    引发故障级联的警告。表示配置验证失败并触发了自动修复。这是应该立即处理的主要症状。
  • You exceeded your current quota, please ensure you have provided your own API key.(错误 429:insufficient_quota
    OpenAI API 响应,确认网关在 API 密钥模式下运行但没有有效/有配额的凭据。
  • Your credit balance is too low
    Anthropic API 响应,表明回退模型也没有有效积分,确认系统范围的认证失败。
  • authentication_required
    当网关检测到没有配置有效认证方法时的内部错误代码。

历史相关问题

  • Issue #1247:"OAuth tokens not persisted after gateway restart" — 令牌仅存储在内存中,重启后丢失。(在 v2025.8.2 中修复)
  • Issue #1156:"Doctor recovery creates config with wrong default auth mode" — 默认值设置为 none 而不是 oauth。(在 v2025.11.0 中修复)
  • Issue #1089:"No logging for auth mode changes" — 请求为所有认证转换添加日志记录。(在 v2025.9.5 中修复,但在重构期间移除了 doctor 恢复路径中的日志记录)
  • Issue #2201:"Config doctor should preserve OAuth tokens from keychain" — 引发此确切错误报告的功能请求。

错误代码参考

1001  CONFIG_INVALID           - Config failed validation
1002  CONFIG_WRITE_FAILED      - Cannot persist config changes
1003  AUTH_MODE_UNSUPPORTED    - Requested auth mode not available
1004  AUTH_TOKEN_EXPIRED       - OAuth/access token has expired
1005  AUTH_TOKEN_INVALID       - Token signature validation failed
1006  AUTH_REFRESH_FAILED      - OAuth token refresh returned error
1007  QUOTA_EXCEEDED           - API quota exhausted (any provider)
1008  CREDENTIAL_MISSING       - Required credential not found in store

依据与来源

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