diff --git a/src-tauri/src/mcp/codex.rs b/src-tauri/src/mcp/codex.rs index 7ab7eef8f..73100beda 100644 --- a/src-tauri/src/mcp/codex.rs +++ b/src-tauri/src/mcp/codex.rs @@ -361,14 +361,11 @@ pub fn sync_single_server_to_codex( let mut doc = if config_path.exists() { let content = std::fs::read_to_string(&config_path).map_err(|e| AppError::io(&config_path, e))?; - // 尝试解析现有配置,如果失败则创建新文档(容错处理) - match content.parse::() { - Ok(doc) => doc, - Err(e) => { - log::warn!("解析 Codex config.toml 失败: {e},将创建新配置"); - toml_edit::DocumentMut::new() - } - } + // 解析失败必须报错而不是用空文档顶替:写回空文档会把用户 + // config.toml 里的其它段落(model/model_providers/注释等)整体清空 + content + .parse::() + .map_err(|e| AppError::McpValidation(format!("解析 config.toml 失败: {e}")))? } else { toml_edit::DocumentMut::new() }; diff --git a/src-tauri/tests/import_export_sync.rs b/src-tauri/tests/import_export_sync.rs index e1e81d24e..f6df65876 100644 --- a/src-tauri/tests/import_export_sync.rs +++ b/src-tauri/tests/import_export_sync.rs @@ -497,6 +497,42 @@ fn sync_enabled_to_codex_returns_error_on_invalid_toml() { } } +#[test] +fn sync_single_server_to_codex_fails_closed_on_invalid_toml() { + let _guard = test_mutex().lock().expect("acquire test mutex"); + reset_test_fs(); + let path = cc_switch_lib::get_codex_config_path(); + if let Some(parent) = path.parent() { + fs::create_dir_all(parent).expect("create codex dir"); + } + // 含用户内容 + 语法错误的 config.toml:同步必须报错且不得覆盖文件 + let broken = "model = \"gpt-5.5\"\ninvalid = [\n"; + fs::write(&path, broken).expect("write invalid config"); + + let config = MultiAppConfig::default(); + let err = cc_switch_lib::sync_single_server_to_codex( + &config, + "srv", + &json!({ "type": "stdio", "command": "echo" }), + ) + .expect_err("sync should fail instead of wiping the file"); + match err { + cc_switch_lib::AppError::McpValidation(msg) => { + assert!( + msg.contains("config.toml"), + "error message should mention config.toml" + ); + } + other => panic!("unexpected error: {other:?}"), + } + + let text = fs::read_to_string(&path).expect("read config.toml"); + assert_eq!( + text, broken, + "invalid config.toml must be left untouched on sync failure" + ); +} + #[test] fn sync_codex_provider_missing_auth_returns_error() { let _guard = test_mutex().lock().expect("acquire test mutex");