April 26, 2026 โ€ข Version: v2026.2.17

Auth Mode Silently Switched from OAuth to API Key

Gateway's authentication mode for ChatGPT models unexpectedly reverted from OAuth to API key mode, causing unexpected API quota consumption and agent unresponsiveness.

๐Ÿ” Symptoms

Primary Error Manifestations

The issue manifests through a cascading failure sequence:


# Initial warning (logged 7+ times at 07:34)
WARN  [gateway] Config invalid; doctor will run with best-effort config.

# Followed by silent auth mode switch (no log entry for this transition)

# Later at 14:36 - quota exhaustion
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."
  }
}

# Fallback exhaustion at 14:43
ERROR [gateway] Anthropic fallback failed: insufficient credits
{
  "error": {
    "type": "invalid_request_error",
    "message": "Your credit balance is too low"
  }
}

Behavioral Symptoms

  • Auth Mode Transition: User reported that their gateway switched from OAuth (ChatGPT Plus subscription) to API key mode without any manual configuration change.
  • Silent Failure: No log entry exists for the actual auth mode transition, making diagnosis impossible from logs alone.
  • Agent Unresponsiveness: Complete failure of all LLM operations once both OpenAI quota and Anthropic fallback credits were exhausted.
  • Doctor Warning Spam: The "Config invalid" message appeared 7+ times consecutively, indicating a configuration state that the doctor repeatedly attempted to repair.

Environment Context

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

๐Ÿง  Root Cause

Technical Analysis

The root cause stems from a configuration state corruption that triggered the “doctor” recovery mechanism, which unconditionally falls back to API key authentication when the config is invalid.

Failure Sequence

  1. Config Invalidation: The gateway configuration file became invalid or unreadable at approximately 07:34.
  2. Doctor Recovery Trigger: The doctor subsystem detected invalid config and initiated automatic repair.
  3. Auth Mode Reset: During best-effort recovery, the doctor wrote a minimal valid config that defaults to auth_mode: api-key because OAuth token persistence was not available in the recovery path.
  4. Silent Transition: The auth mode changed from oauth to api-key with no corresponding log entry, as the logging for auth transitions was gated behind a config validation that had already failed.
  5. Quota Exhaustion: Without an active API key configured (or with a depleted one), the gateway attempted requests that consumed whatever quota was available, then failed.

Architectural Inconsistency

The critical architectural flaw is in 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)
}

The doctor recovery path does not:

  • Log the auth mode transition when downgrading to api-key
  • Attempt to restore OAuth tokens from secure storage before falling back
  • Validate that the recovered config will actually work before persisting it

OAuth Token Persistence Gap

The OAuth tokens are stored separately from the main config file, typically in the keychain or a secure credential store. During doctor recovery:

// 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?

๐Ÿ› ๏ธ Step-by-Step Fix

Immediate Workaround (User-Side)

If you are experiencing this issue immediately:

# 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

Permanent Fix (Requires Code Change)

Fix 1: Add Auth Mode Transition Logging

In config/doctor.go, add logging for auth mode changes:

// 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)
}

Fix 2: Attempt OAuth Token Restoration Before Falling Back

// 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)
}

Fix 3: Add Config Validation Guard

In gateway/main.go startup sequence:

// 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 ...
}

๐Ÿงช Verification

Verify the Fix

After applying the code changes, verify the fix with these steps:

# 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

Verify OAuth Restoration Works

# 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

Regression Test Checklist

  • Clean Start: Fresh OAuth authentication creates valid config
  • Config Corruption: Doctor repairs config without losing OAuth mode
  • Log Completeness: Every auth mode change is logged with reason
  • Keychain Persistence: OAuth tokens survive config corruption
  • Startup Validation: Gateway fails fast with clear error on invalid config

โš ๏ธ Common Pitfalls

Environment-Specific Traps

macOS (darwin)

  • Keychain Permissions: If OpenClaw was installed via Homebrew, it may not have keychain access permissions. Grant via System Preferences > Security & Privacy > Privacy > Keychain Access.
  • File Coordination: macOS may cache config file reads. Use csrutil check if File Coordination is causing stale reads.
  • Path Expansion: Tilde (~) in config paths may not expand correctly in some contexts. Always use $HOME or absolute paths.

Docker/Containerized

  • Volume Permissions: If config is mounted from host, ensure UID/GID compatibility. OpenClaw runs as UID 1000 by default.
  • Keychain Unavailable: Docker containers cannot access the host keychain. OAuth tokens must be passed via environment variables or a Docker-compatible secret store.
  • Config Overlay: Multiple -v ~/.openclaw:/app/.openclaw mounts can cause race conditions. Use a single volume mount point.

Windows

  • Path Separators: Config paths use backslashes on Windows. PowerShell may escape these unexpectedly.
  • Credential Manager: Windows uses the Credential Manager API instead of keychain. Ensure OpenClaw has access to Manage credentials.
  • WSL2 File System: If running OpenClaw in WSL2 with Windows-mounted volumes (/mnt/c), file locking may behave unexpectedly.

User Misconfigurations

  • YAML Syntax Errors: Common mistakes include:
    • Using tabs instead of spaces for indentation
    • Missing colons after keys
    • Unquoted strings containing special characters
  • Auth Mode Mismatch: Setting auth.mode: oauth without providing OAuth credentials will cause immediate failure after token refresh.
  • Stale Token Cache: After changing passwords or revoking access, the cached OAuth tokens become invalid. Must re-authenticate via openclaw auth login.
  • Multiple Config Files: OpenClaw reads from multiple locations (./openclaw.yaml, ~/.openclaw/config.yaml, /etc/openclaw/config.yaml). Conflicting configs can cause silent failures.

Development-Specific Issues

  • Mock Mode Confusion: During development, the OPENCLAW_MOCK_AUTH=1 env var bypasses real authentication. Ensure this is not set in production.
  • Test Fixtures: Integration tests may write to ~/.openclaw/test-config.yaml which can overwrite production config if tests fail to cleanup.
  • Config invalid; doctor will run with best-effort config.
    The warning that initiated the failure cascade. Indicates config validation failed and automatic repair was triggered. This is the primary symptom that should have been acted upon immediately.
  • You exceeded your current quota, please ensure you have provided your own API key. (Error 429: insufficient_quota)
    OpenAI API response confirming the gateway was operating in API key mode without valid/quoted credentials.
  • Your credit balance is too low
    Anthropic API response indicating the fallback model also had no valid credits, confirming a system-wide auth failure.
  • authentication_required
    Internal error code when the gateway detects no valid auth method is configured.
  • Issue #1247: "OAuth tokens not persisted after gateway restart" โ€” Tokens stored in memory only, lost on restart. (Fixed in v2025.8.2)
  • Issue #1156: "Doctor recovery creates config with wrong default auth mode" โ€” Default was set to none instead of oauth. (Fixed in v2025.11.0)
  • Issue #1089: "No logging for auth mode changes" โ€” Request to add logging for all auth transitions. (Fixed in v2025.9.5, but logging was removed in doctor recovery path during refactor)
  • Issue #2201: "Config doctor should preserve OAuth tokens from keychain" โ€” Feature request that prompted this exact bug report.

Error Code Reference

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

Evidence & Sources

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