{"record":{"id":"58d3ef34bea62a24","repo":"BigPizzaV3/CodexPlusPlus","slug":"error-settings-json-codex","errorCode":null,"errorMessage":"切换供应商失败：{error}；同时回滚配置失败：settings.json={}，Codex 实时文件={}","messagePattern":"切换供应商失败：(.+?)；同时回滚配置失败：settings\\.json=(.+?)，Codex 实时文件=(.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"critical","filePath":"crates/codex-plus-core/src/relay_switch.rs","lineNumber":55,"sourceCode":"\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) => {\n            crate::codex_app_state::sync_app_state_after_provider_switch_nonfatal(\n                home,\n                \"relay_switch.after\",\n            );\n            Ok(result)\n        }\n        Err(error) => {\n            let settings_restore_error = store.save(&original_settings).err();\n            let live_restore_error = live_snapshot.restore(home).err();\n            if settings_restore_error.is_some() || live_restore_error.is_some() {\n                anyhow::bail!(\n                    \"切换供应商失败：{error}；同时回滚配置失败：settings.json={}，Codex 实时文件={}\",\n                    settings_restore_error\n                        .map(|error| error.to_string())\n                        .unwrap_or_else(|| \"ok\".to_string()),\n                    live_restore_error\n                        .map(|error| error.to_string())\n                        .unwrap_or_else(|| \"ok\".to_string())\n                );\n            }\n            Err(error)\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\nstruct LiveFilesSnapshot {\n    config: Option<Vec<u8>>,\n    auth: Option<Vec<u8>>,","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/1f431ae49b57b3055e0e6845ba6156c6b4232b4d/crates/codex-plus-core/src/relay_switch.rs#L37-L73","documentation":"switch_relay_profile_in_home 的失败恢复路径：apply_selected_relay_profile 出错后，会尝试两项回滚——store.save(original_settings) 恢复 settings.json、live_snapshot.restore(home) 恢复 config.toml/auth.json。任一回滚也失败时，抛出这条复合错误，把「原始切换错误 + settings.json 回滚结果 + 实时文件回滚结果」三段拼在一起。出现它意味着系统处于未知的中间状态，必须人工核对文件。","triggerScenarios":"切换供应商中途失败（例如 profile 校验失败、TOML 解析失败），且随后 settings.json 写回或 config.toml/auth.json 恢复也失败（磁盘满、目录只读、文件被其他进程占用、权限被回收）。","commonSituations":"home 目录权限被改动；codex 进程正持有 auth.json；磁盘空间耗尽导致原子写失败；杀毒/同步软件（OneDrive 等）锁定配置文件。","solutions":["先读消息第一段「切换供应商失败：{error}」确定切换本身失败的原因并修复","看消息尾部 settings.json= 与 Codex 实时文件= 哪个不是 ok，针对该文件检查权限/占用/磁盘空间","关闭占用 config.toml / auth.json 的进程（codex、同步工具）后重试切换","若文件已不一致，从 backup_path 指向的备份手工恢复 config.toml，再手动核对 settings.json"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match switch_relay_profile_in_home(&store, &home, next, &prev_id) {\n    Ok(result) => Ok(result),\n    Err(err) if err.to_string().contains(\"同时回滚配置失败\") => {\n        // 复合失败：解析三段（切换错误 / settings.json / 实时文件），\n        // 停止一切自动重试；提示用户关闭占用进程、检查权限与磁盘，\n        // 并引导从 backup_path 备份手工恢复 config.toml\n        report_inconsistent_state(&err);\n        Err(err)\n    }\n    Err(err) => Err(err),\n}","preventionTips":["切换前确认 home 目录可写、磁盘空间充足","切换时避免 codex / 同步软件同时持有 config.toml、auth.json","保留并展示 backup_path，作为手工恢复的最后手段","该错误出现后不要原地重试同一操作，先恢复一致性"],"tags":["relay-switch","rollback","atomic-write","recovery"],"backgroundTag":"transaction-rollback-failure","analyzedSha":"1f431ae49b57b3055e0e6845ba6156c6b4232b4d","analyzedAt":"2026-08-16T20:54:18.598Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}