BigPizzaV3/CodexPlusPlus · error · anyhow::Error

官方混合 API 不应在 auth.json 中保存 OPENAI_API_KEY。请清理此供应商的 auth.json

Error message

官方混合 API 不应在 auth.json 中保存 OPENAI_API_KEY。请清理此供应商的 auth.json 后再切换。

What it means

Thrown by validate_switch_profile_files during apply_selected_relay_profile (called from switch_relay_profile_in_home). It fires when a provider profile with relay_mode == Official and official_mix_api_key == true carries an auth_contents JSON object whose OPENAI_API_KEY is a non-empty string. The official 'hybrid' mode is expected to authenticate with ChatGPT OAuth tokens, not an API key, so the switch is aborted before any config.toml or auth.json is written to keep the previous provider active.

Source

Thrown at crates/codex-plus-core/src/relay_switch.rs:190

                profile.id.as_str()
            } else {
                profile.name.as_str()
            }
        );
    }
    if profile.relay_mode == RelayMode::Official
        && serde_json::from_str::<serde_json::Value>(&profile.auth_contents)
            .ok()
            .and_then(|value| {
                value
                    .get("OPENAI_API_KEY")
                    .and_then(serde_json::Value::as_str)
                    .map(str::trim)
                    .map(str::is_empty)
            })
            == Some(false)
    {
        anyhow::bail!(
            "官方混合 API 不应在 auth.json 中保存 OPENAI_API_KEY。请清理此供应商的 auth.json 后再切换。"
        );
    }
    Ok(())
}

fn relay_combined_common_config(settings: &BackendSettings) -> String {
    let sections = [
        settings.relay_common_config_contents.trim(),
        settings.relay_context_config_contents.trim(),
    ]
    .into_iter()
    .filter(|section| !section.is_empty())
    .collect::<Vec<_>>();
    if sections.is_empty() {
        String::new()
    } else {
        crate::relay_config::normalize_config_text(&format!("{}\n", sections.join("\n\n")))

View on GitHub (pinned to 1f431ae49b)

Solutions

  1. Open the profile in the manager and remove the OPENAI_API_KEY entry from its auth.json (auth_contents), leaving {} or the OAuth tokens, then switch again
  2. If the profile really is a Pure API provider, set its relay_mode to PureApi instead of Official
  3. If you intended key-based official access, set official_mix_api_key = true is already required; the key must live in config.toml (model_provider apiKey), not auth.json — move it there and clear auth_contents
  4. Programmatically strip the field before switching: parse auth_contents, remove OPENAI_API_KEY, save the profile

Example fix

// before (profile.auth_contents)
{"OPENAI_API_KEY":"sk-abc123"}
// after
{}
Defensive patterns

Strategy: validation

Validate before calling

fn can_switch_official_mix(profile: &RelayProfile) -> bool {
    if profile.relay_mode != RelayMode::Official || !profile.official_mix_api_key {
        return true;
    }
    serde_json::from_str::<serde_json::Value>(&profile.auth_contents)
        .ok()
        .and_then(|v| v.get("OPENAI_API_KEY").and_then(Value::as_str))
        .map(|k| k.trim().is_empty())
        .unwrap_or(true)
}

if !can_switch_official_mix(&profile) {
    // surface a form error instead of calling switch_relay_profile_in_home
}

Try / catch

match switch_relay_profile_in_home(home, &mut settings) {
    Err(e) if e.to_string().contains("OPENAI_API_KEY") => {
        // prompt user to clean the profile's auth.json; keep previous provider active
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling switch_relay_profile_in_home (or the bridge '/settings/switch' style flow that reaches apply_selected_relay_profile) for a RelayProfile where relay_mode == RelayMode::Official, official_mix_api_key == true, and serde_json::from_str(profile.auth_contents) yields an object with a string field OPENAI_API_KEY whose trimmed value is non-empty. Note: Official profiles with official_mix_api_key == false never reach this check (they take the clear-config branch at relay_switch.rs:137).

Common situations: Importing a provider from ccs_import/provider_import, which generates auth_contents as {"OPENAI_API_KEY": "sk-..."}, and then flipping that profile to Official hybrid mode; hand-editing the profile's auth.json in the manager UI and pasting an API key; migrating an old Pure API profile to Official mode without clearing the key.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@1f431ae49b (2026-08-16). Data as JSON: /api/errors/36848f335303dc81. Report an issue: GitHub.