mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-28 16:56:16 +08:00
fix(proxy): harden takeover-residue recovery across config-dir switches (#4076)
Changing app_config_dir relocates the SQLite database, so a restart triggered while proxy takeover is active used to strand the live configs: the new instance reads a fresh DB with no live backups, the first-run import then persisted the PROXY_MANAGED placeholder as the `default` provider, and the no-backup recovery path wrote that placeholder right back to the live files — leaving Claude/Codex/Gemini pointed at a dead local proxy with no automatic way out. Three orthogonal fixes, defense in depth: - restart_app now awaits cleanup_before_exit() before app.restart(). Since #4069 the ExitRequested handler intentionally defers restart requests to Tauri's default re-exec without custom cleanup, which is correct for same-DB restarts but not for this command's dir-change use case: only the old instance holds the backups needed to restore the taken-over live files, so it must restore them while its event loop is still alive. - import_default_config refuses to import a live config that is under proxy takeover (placeholder detected), instead of persisting it as the current provider. - restore_live_from_ssot_for_app validates that the current provider's settings_config does not itself contain takeover placeholders before writing it back; polluted SSOT now falls through to the placeholder cleanup fallback. Regression tests cover the import guard and the no-backup recovery path (the latter fails before this change by writing PROXY_MANAGED back to live).
This commit is contained in:
@@ -1133,6 +1133,22 @@ pub fn import_default_config(state: &AppState, app_type: AppType) -> Result<bool
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// 拒绝把"被代理接管的 Live"导入为供应商:接管期间 Live 里只有
|
||||
// PROXY_MANAGED 占位符和本地代理地址,不是用户的真实配置。一旦导入,
|
||||
// 它会成为 current provider(SSOT),后续"无备份恢复"路径会把占位符
|
||||
// 当真实配置写回 Live,永久卡在已失效的本地代理上。
|
||||
// 典型触发场景:代理接管开启时切换 app_config_dir 并重启,新数据库首启导入。
|
||||
if state
|
||||
.proxy_service
|
||||
.detect_takeover_in_live_config_for_app(&app_type)
|
||||
{
|
||||
return Err(AppError::localized(
|
||||
"provider.import.live_taken_over",
|
||||
"Live 配置当前处于代理接管状态(包含占位符),不能导入为供应商。请先关闭代理接管或恢复 Live 配置后重试。",
|
||||
"The live config is currently taken over by the proxy (contains placeholders) and cannot be imported as a provider. Disable proxy takeover or restore the live config first.",
|
||||
));
|
||||
}
|
||||
|
||||
let settings_config = match app_type {
|
||||
AppType::Codex => crate::codex_config::read_codex_live_settings()?,
|
||||
AppType::Claude => {
|
||||
|
||||
@@ -1632,7 +1632,7 @@ impl ProxyService {
|
||||
///
|
||||
/// 返回值:
|
||||
/// - Ok(true):已成功写回
|
||||
/// - Ok(false):缺少当前供应商/供应商不存在,无法写回
|
||||
/// - Ok(false):缺少当前供应商/供应商不存在/供应商本身含占位符,无法写回
|
||||
fn restore_live_from_ssot_for_app(&self, app_type: &AppType) -> Result<bool, String> {
|
||||
let current_id = crate::settings::get_effective_current_provider(&self.db, app_type)
|
||||
.map_err(|e| format!("获取 {app_type:?} 当前供应商失败: {e}"))?;
|
||||
@@ -1650,6 +1650,16 @@ impl ProxyService {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
// 供应商配置本身含接管占位符时不可写回(历史异常:接管期间 Live 被
|
||||
// 误导入成了供应商)。写回只会把占位符固化进 Live;返回 Ok(false)
|
||||
// 让调用方落到"清理占位符"兜底。
|
||||
if Self::live_has_proxy_placeholder_for_app(app_type, &provider.settings_config) {
|
||||
log::warn!(
|
||||
"{app_type:?} 当前供应商配置含代理接管占位符(疑似接管期间被导入的残留),跳过 SSOT 写回,改走占位符清理"
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
write_live_with_common_config(self.db.as_ref(), app_type, provider)
|
||||
.map_err(|e| format!("写入 {app_type:?} Live 配置失败: {e}"))?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user