认证模式静默从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 密钥认证。
故障序列
- 配置失效:网关配置文件在约 07:34 变得无效或不可读取。
- Doctor 恢复触发:
doctor子系统检测到配置无效并启动自动修复。 - 认证模式重置:在尽力恢复期间,doctor 写入了最小有效配置,由于恢复路径中 OAuth 令牌持久化不可用,默认设置为
auth_mode: api-key。 - 静默转换:认证模式从
oauth更改为api-key,没有对应的日志条目,因为认证转换的日志记录被卡在已经失败的配置验证之后。 - 配额耗尽:没有配置有效的 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