From d2823cef81ca5267f5cc9183abad90c96efa2bab Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 11 Jul 2026 00:16:29 +0800 Subject: [PATCH] fix: refresh claude live config after common updates during takeover --- src-tauri/src/services/provider/mod.rs | 11 ++ src-tauri/src/services/proxy.rs | 25 +++-- src-tauri/tests/provider_commands.rs | 139 +++++++++++++++++++++++++ 3 files changed, 166 insertions(+), 9 deletions(-) diff --git a/src-tauri/src/services/provider/mod.rs b/src-tauri/src/services/provider/mod.rs index 7e1a53f5..bc1fa999 100644 --- a/src-tauri/src/services/provider/mod.rs +++ b/src-tauri/src/services/provider/mod.rs @@ -776,6 +776,7 @@ impl ProviderService { action.app_type.as_str(), &action.provider, action.previous_provider.as_ref(), + action.common_config_snippet.as_deref(), )) .map_err(AppError::Message)?; PreparedPostCommitEffect::ProxyLiveBackup(backup_snapshot) @@ -822,6 +823,16 @@ impl ProviderService { .save_live_backup_snapshot(prepared.action.app_type.as_str(), snapshot), ) .map_err(AppError::Message)?; + + // Refresh active live settings under takeover + if matches!(prepared.action.app_type, AppType::Claude) { + futures::executor::block_on( + state.proxy_service.sync_claude_live_from_provider_while_proxy_active( + &prepared.action.provider, + ), + ) + .map_err(AppError::Message)?; + } } } diff --git a/src-tauri/src/services/proxy.rs b/src-tauri/src/services/proxy.rs index 9135c1ea..60cb64e4 100644 --- a/src-tauri/src/services/proxy.rs +++ b/src-tauri/src/services/proxy.rs @@ -1591,7 +1591,7 @@ impl ProxyService { original_live: &Value, provider: &Provider, ) -> Result { - let mut provider_snapshot = self.build_live_snapshot_from_provider(app_type, provider)?; + let mut provider_snapshot = self.build_live_snapshot_from_provider(app_type, provider, None)?; Self::apply_codex_unified_session_bucket_to_backup( app_type, provider, @@ -2086,7 +2086,7 @@ impl ProxyService { ) -> Result<(), String> { let app_type_enum = Self::takeover_app_from_str(app_type)?; let mut backup_snapshot = - self.build_live_snapshot_from_provider(&app_type_enum, provider)?; + self.build_live_snapshot_from_provider(&app_type_enum, provider, None)?; Self::apply_codex_unified_session_bucket_to_backup( &app_type_enum, provider, @@ -2108,9 +2108,11 @@ impl ProxyService { app_type: &str, provider: &Provider, previous_provider: Option<&Provider>, + snippet_override: Option<&str>, ) -> Result { let app_type = Self::takeover_app_from_str(app_type)?; - let mut backup_snapshot = self.build_live_snapshot_from_provider(&app_type, provider)?; + let mut backup_snapshot = + self.build_live_snapshot_from_provider(&app_type, provider, snippet_override)?; Self::apply_codex_unified_session_bucket_to_backup( &app_type, provider, @@ -2118,7 +2120,8 @@ impl ProxyService { )?; let previous_backup_snapshot = previous_provider .map(|provider| { - let mut snapshot = self.build_live_snapshot_from_provider(&app_type, provider)?; + let mut snapshot = + self.build_live_snapshot_from_provider(&app_type, provider, snippet_override)?; Self::apply_codex_unified_session_bucket_to_backup( &app_type, provider, @@ -2382,6 +2385,7 @@ impl ProxyService { app_type_enum.as_str(), &provider, previous_provider.as_ref(), + None, ) .await?; self.save_live_backup_snapshot(app_type_enum.as_str(), &backup_snapshot) @@ -2407,7 +2411,7 @@ impl ProxyService { ) -> Result<(), String> { let mut effective_provider = provider.clone(); effective_provider.settings_config = - self.build_live_snapshot_from_provider(&AppType::Claude, provider)?; + self.build_live_snapshot_from_provider(&AppType::Claude, provider, None)?; let mut effective_settings = effective_provider.settings_config.clone(); let (proxy_url, _) = self.build_proxy_urls_for_app(&AppType::Claude).await?; @@ -3099,7 +3103,7 @@ impl ProxyService { if let Some(provider) = provider_for_write { let mut effective_provider = provider.clone(); effective_provider.settings_config = - self.build_live_snapshot_from_provider(app_type, provider)?; + self.build_live_snapshot_from_provider(app_type, provider, None)?; Self::apply_claude_takeover_fields_for_provider( live, proxy_url, @@ -3457,16 +3461,19 @@ impl ProxyService { &self, app_type: &AppType, provider: &Provider, + snippet_override: Option<&str>, ) -> Result { - let common_config_snippet = - self.db + let common_config_snippet = match snippet_override { + Some(override_snippet) => Some(override_snippet.to_string()), + None => self.db .get_config_snippet(app_type.as_str()) .map_err(|error| { format!( "load common config snippet for {} failed: {error}", app_type.as_str() ) - })?; + })?, + }; let apply_common_config = crate::services::provider::ProviderService::provider_uses_common_config_for_app( app_type, diff --git a/src-tauri/tests/provider_commands.rs b/src-tauri/tests/provider_commands.rs index 84b59176..62f136f5 100644 --- a/src-tauri/tests/provider_commands.rs +++ b/src-tauri/tests/provider_commands.rs @@ -2035,6 +2035,145 @@ async fn switch_provider_under_takeover_refreshes_claude_restore_backup_to_selec ); } +#[tokio::test] +#[serial] +async fn updating_claude_common_config_under_takeover_refreshes_active_live_settings() { + let _guard = lock_test_mutex(); + reset_test_fs(); + let _home = ensure_test_home(); + + let settings_path = get_claude_settings_path(); + if let Some(parent) = settings_path.parent() { + std::fs::create_dir_all(parent).expect("create claude settings dir"); + } + + let provider_live = json!({ + "env": { + "ANTHROPIC_API_KEY": "provider-key" + }, + "workspace": { + "path": "/tmp/provider-workspace" + } + }); + std::fs::write( + &settings_path, + serde_json::to_string_pretty(&provider_live).expect("serialize provider live"), + ) + .expect("seed claude live config"); + + let mut config = MultiAppConfig::default(); + { + let manager = config + .get_manager_mut(&AppType::Claude) + .expect("claude manager"); + manager.current = "glm-provider".to_string(); + let mut provider = Provider::with_id( + "glm-provider".to_string(), + "GLM Claude".to_string(), + json!({ + "env": { "ANTHROPIC_API_KEY": "provider-key" }, + "workspace": { "path": "/tmp/provider-workspace" } + }), + None, + ); + provider.meta = Some(cc_switch_lib::ProviderMeta { + apply_common_config: Some(true), + ..Default::default() + }); + manager.providers.insert("glm-provider".to_string(), provider); + } + + let state = state_from_config(config); + state.save().expect("persist provider state to db"); + + let proxy_port = find_free_port(); + state + .db + .set_app_proxy_preferred_port("claude", proxy_port) + .expect("update claude proxy port"); + + state + .proxy_service + .set_takeover_for_app("claude", true) + .await + .expect("enable claude takeover"); + + let snippet = serde_json::json!({ + "env": { + "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1" + }, + "statusLine": { + "type": "command", + "command": "~/.claude/statusline.sh" + } + }) + .to_string(); + + ProviderService::set_common_config_snippet(&state, AppType::Claude, Some(snippet)) + .expect("set common config under takeover"); + + let live_during_takeover: serde_json::Value = + read_json_file(&settings_path).expect("read claude live settings under takeover"); + assert_eq!( + live_during_takeover + .get("env") + .and_then(|env| env.get("ANTHROPIC_BASE_URL")) + .and_then(|value| value.as_str()), + Some(format!("http://127.0.0.1:{proxy_port}").as_str()), + "common config apply under takeover should keep Claude live config pointed at the local proxy" + ); + assert_eq!( + live_during_takeover + .get("env") + .and_then(|env| env.get("ANTHROPIC_API_KEY")) + .and_then(|value| value.as_str()), + Some("PROXY_MANAGED"), + "common config apply under takeover should keep the managed placeholder in Claude live config" + ); + assert_eq!( + live_during_takeover + .get("env") + .and_then(|env| env.get("CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC")) + .and_then(|value| value.as_str()), + Some("1"), + "common config apply under takeover should refresh active live config with snippet-owned env keys" + ); + assert_eq!( + live_during_takeover + .get("statusLine") + .and_then(|value| value.get("command")) + .and_then(|value| value.as_str()), + Some("~/.claude/statusline.sh"), + "common config apply under takeover should refresh active live config with snippet-owned nested keys" + ); + + let backup_after_apply = state + .db + .get_live_backup("claude") + .await + .expect("read claude live backup after common config apply") + .expect("claude takeover backup should exist after common config apply"); + let backup_after_apply: serde_json::Value = + serde_json::from_str(&backup_after_apply.original_config) + .expect("parse claude live backup after common config apply"); + assert_eq!( + backup_after_apply + .get("env") + .and_then(|env| env.get("CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC")) + .and_then(|value| value.as_str()), + Some("1"), + "common config apply under takeover should refresh the restore backup too" + ); + assert_eq!( + backup_after_apply + .get("statusLine") + .and_then(|value| value.get("command")) + .and_then(|value| value.as_str()), + Some("~/.claude/statusline.sh"), + "common config apply under takeover should store nested snippet keys in the restore backup" + ); +} + fn seed_claude_provider(config: &mut MultiAppConfig, id: &str, name: &str) { let manager = config .get_manager_mut(&AppType::Claude)