{"record":{"id":"ff4d4d10503cda61","repo":"BigPizzaV3/CodexPlusPlus","slug":"config-toml-auth-json","errorCode":null,"errorMessage":"供应商配置总开关已关闭，未写入 config.toml / auth.json。","messagePattern":"供应商配置总开关已关闭，未写入 config\\.toml / auth\\.json。","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"crates/codex-plus-core/src/relay_switch.rs","lineNumber":26,"sourceCode":"};\nuse crate::settings::{BackendSettings, RelayMode, SettingsStore};\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct RelaySwitchResult {\n    pub settings: BackendSettings,\n    pub configured: bool,\n    pub backup_path: Option<String>,\n}\n\npub fn switch_relay_profile_in_home(\n    store: &SettingsStore,\n    home: &Path,\n    next_settings: BackendSettings,\n    previous_active_relay_id: &str,\n) -> anyhow::Result<RelaySwitchResult> {\n    let mut selected_settings = next_settings;\n    if !selected_settings.relay_profiles_enabled {\n        anyhow::bail!(\"供应商配置总开关已关闭，未写入 config.toml / auth.json。\");\n    }\n    crate::codex_app_state::capture_app_state_snapshot_nonfatal(home, \"relay_switch.before\");\n\n    let original_settings = store.load().unwrap_or_default();\n    let live_snapshot = LiveFilesSnapshot::capture(home).context(\"读取当前 Codex 实时配置失败\")?;\n    if !previous_active_relay_id.trim().is_empty()\n        && previous_active_relay_id != selected_settings.active_relay_id\n    {\n        backfill_profile_before_switch(home, &mut selected_settings, previous_active_relay_id)?;\n    }\n\n    store\n        .save(&selected_settings)\n        .context(\"保存供应商设置失败\")?;\n    let selected_settings = store.load().context(\"读取供应商设置失败\")?;\n\n    match apply_selected_relay_profile(home, &selected_settings) {\n        Ok(result) => {","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/1f431ae49b57b3055e0e6845ba6156c6b4232b4d/crates/codex-plus-core/src/relay_switch.rs#L8-L44","documentation":"switch_relay_profile_in_home 是切换供应商的总入口。第一步就检查 BackendSettings.relay_profiles_enabled（供应商配置总开关）：为 false 时直接 bail，不读快照、不写 settings.json、不碰 config.toml / auth.json。这是一种「拒绝执行」守卫而非故障——系统状态完全未被改动。","triggerScenarios":"在 relay_profiles_enabled == false 的 settings 上调用 switch_relay_profile_in_home（包括 UI 点切换、定时任务、导入后自动切换等任何路径）。","commonSituations":"用户关掉了「供应商配置」总开关后，残留的切换调用（前端按钮未同步禁用）；settings.json 被外部工具改写把开关置 false；自动化脚本未检查开关状态。","solutions":["在设置里重新打开「供应商配置总开关」后重试","调用方在调用前检查 next_settings.relay_profiles_enabled","UI 在开关关闭时禁用切换按钮，避免无效调用"],"exampleFix":"// before\nswitch_relay_profile_in_home(&store, &home, next, &prev_id)?;\n\n// after\nif !next.relay_profiles_enabled {\n    return Err(anyhow::anyhow!(\"供应商配置总开关已关闭，未写入 config.toml / auth.json。\"));\n}\nswitch_relay_profile_in_home(&store, &home, next, &prev_id)?;","handlingStrategy":"validation","validationCode":"if !next_settings.relay_profiles_enabled {\n    return Err(anyhow::anyhow!(\n        \"供应商配置总开关已关闭，未写入 config.toml / auth.json。\"\n    ));\n}\nswitch_relay_profile_in_home(&store, &home, next_settings, previous_active_relay_id)?;","typeGuard":null,"tryCatchPattern":"match switch_relay_profile_in_home(&store, &home, next, &prev_id) {\n    Ok(result) => { /* ... */ }\n    Err(err) if err.to_string().contains(\"总开关已关闭\") => {\n        // 引导用户打开开关；不要自动改 settings 强行切换\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["调用前检查 settings.relay_profiles_enabled","UI 在总开关关闭时禁用一切切换入口","把该错误视为用户意图拒绝（warning），不要静默重试"],"tags":["relay-switch","feature-flag","guard","codex-plus-core"],"backgroundTag":"feature-flag-disabled","analyzedSha":"1f431ae49b57b3055e0e6845ba6156c6b4232b4d","analyzedAt":"2026-08-16T20:54:18.598Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}