Skip to content

fix(claude): preserve live common config on provider switch & fix auto-inference default#3728

Open
wait4xx wants to merge 3 commits into
farion1231:mainfrom
wait4xx:fix/claude-preserve-common-config
Open

fix(claude): preserve live common config on provider switch & fix auto-inference default#3728
wait4xx wants to merge 3 commits into
farion1231:mainfrom
wait4xx:fix/claude-preserve-common-config

Conversation

@wait4xx
Copy link
Copy Markdown

@wait4xx wait4xx commented Jun 4, 2026

Closes #3631

Summary / 概述

Three overlapping bugs cause statusLine, enabledPlugins, model, and other common fields to be lost or overwritten with stale values when switching Claude Code providers.

本 PR 修复了三个叠加的 Bug,这些 Bug 会导致切换 Claude Code Provider 时 statusLine(claude-hud 状态栏)、enabledPlugins(插件列表)、model 等通用配置字段丢失或被旧值覆盖。

Note: Codex 端的同类问题已在 #3700 中记录,PR #3697 正在处理。本 PR 解决 Claude Code 端的同类问题,采用类似的修复思路。

Bug 1: provider_uses_common_config() 自动推断逻辑失效(live.rs:354

对于未显式设置 meta.commonConfigEnabled 的 provider(如内置的 claude-official),None 分支使用 json_is_subset 检查 provider 的 settings_config 是否包含 common config 的所有 key。但 provider 的 settings_config 只存储供应商专属字段(ANTHROPIC_AUTH_TOKENANTHROPIC_BASE_URL 等),永远不包含 statusLineenabledPluginsmodel 这些通用字段。子集检查始终失败,common config 永远不会被合并。

结果:切换到 claude-official 时,settings.json 只写入了 {"env": {}},所有通用配置丢失。

Bug 2: Common config 永远不会重新同步(lib.rs:1626

initialize_common_config_snippets() 仅在首次启动时自动抽取 common config。一旦 common_config_claude 写入数据库,should_auto_extract_config_snippet() 返回 false,snippet 永远不会更新。当插件(如 claude-hud)更新导致缓存路径变化(如 claude-hud/dist/claude-hud/0.1.0/dist/),数据库中的旧路径在每次切换时都会被写回 settings.json

Bug 3: 切换流程不回写 common config(mod.rs:1660

switch_normal() 从数据库读取 common config 合并到 settings.json,但不会将最新的 live settings 同步回数据库。回填步骤(backfill)从 live settings 中剥离了 common config 后直接丢弃——只有供应商专属部分被存回了数据库。

修复内容

  1. 修复 provider_uses_common_config 默认推断live.rs):排他模式应用(Claude/Codex/Gemini)只要存在非空 snippet 就默认合并 common config;additive 模式应用(Hermes/OpenClaw)保留原有 json_is_subset 推断逻辑。

  2. 新增 sync_common_config_from_live()live.rs):从当前 live settings.json 重新抽取 common config 并持久化到数据库,确保 snippet 始终是最新的。

  3. switch_normal() 中调用 sync_common_config_from_live()mod.rs):回填完成后、写入目标 provider 之前,同步最新的 common config 到数据库。

Related Issue / 关联 Issue

Fixes #3631

Related: #3700, #3697 (Codex 同类问题及修复 PR)

Screenshots / 截图

不适用(Rust 后端逻辑修复,无 UI 变化)

Checklist / 检查清单

  • 无前端 TypeScript 修改,pnpm typecheck 不适用
  • 无前端代码修改,pnpm format:check 不适用
  • cargo clippy passes(本地无 Rust 工具链,已通过静态分析验证类型、import 和函数签名)
  • 无用户可见文本修改,国际化文件无需更新

Note on Tests / 测试说明

This PR does not include regression tests due to the lack of a local Rust toolchain. Below are the intended test cases for a contributor with a Rust environment to implement:

本 PR 因本地缺少 Rust 工具链未能附带回归测试。以下为设计的测试用例,供有 Rust 环境的 contributor 实现:

Test 1: provider_service_switch_claude_preserves_common_config_fields

  • Seed live settings.json with statusLine, enabledPlugins, model
  • Create two providers without commonConfigEnabled set (relying on auto-inference)
  • Switch from provider A to provider B
  • Assert: provider-specific fields (ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL) are updated
  • Assert: common fields (statusLine, enabledPlugins, model) are preserved

Test 2: provider_service_switch_claude_syncs_common_config_snippet

  • Seed live settings.json with an updated claude-hud path (with version directory)
  • Pre-seed DB common_config_claude with a stale path (without version directory)
  • Switch provider
  • Assert: common_config_claude in DB is updated to reflect the live path

These tests should follow the existing patterns in src-tauri/tests/provider_service.rs (see provider_service_switch_claude_updates_live_and_state at line 1592 for reference).

Reproduction Verification / 复现验证

Before fix / 修复前:

$ sqlite3 ~/.cc-switch/cc-switch.db "SELECT meta FROM providers WHERE id = 'claude-official'"
{}   # ← no commonConfigEnabled → auto-inference fails → common config not merged

$ ls ~/.claude/plugins/cache/claude-hud/claude-hud/dist/index.js
No such file or directory   # ← stale path from DB, file doesn't exist

After fix / 修复后: common config is always merged for exclusive-mode apps, and the snippet is re-synced from live settings on every switch.

Co-authored-by: GLM 5.1


代码 Diff(供参考,已提交到分支)

diff --git a/src-tauri/src/services/provider/live.rs b/src-tauri/src/services/provider/live.rs
index 8a6b538..21ee83f 100644
--- a/src-tauri/src/services/provider/live.rs
+++ b/src-tauri/src/services/provider/live.rs
@@ -362,9 +362,22 @@ pub(crate) fn provider_uses_common_config(
         .and_then(|meta| meta.common_config_enabled)
     {
         Some(explicit) => explicit && snippet.is_some_and(|value| !value.trim().is_empty()),
-        None => snippet.is_some_and(|value| {
-            settings_contain_common_config(app_type, &provider.settings_config, value)
-        }),
+        None => {
+            // For exclusive-mode apps (Claude/Codex/Gemini), settings.json is fully
+            // overwritten on provider switch. Not merging common config means all
+            // common fields (statusLine, enabledPlugins, model, etc.) are lost.
+            // Default to applying common config as long as a non-empty snippet exists.
+            //
+            // For additive-mode apps (Hermes/OpenClaw), multiple providers coexist
+            // in the same config file — keep the existing subset heuristic.
+            if !app_type.is_additive_mode() {
+                snippet.is_some_and(|v| !v.trim().is_empty())
+            } else {
+                snippet.is_some_and(|value| {
+                    settings_contain_common_config(app_type, &provider.settings_config, value)
+                })
+            }
+        }
     }
 }
 
@@ -528,6 +541,31 @@ pub(crate) fn write_live_with_common_config(
     write_live_snapshot(app_type, &effective_provider)
 }
 
+/// Re-extract common config from the current live settings and persist it to the database.
+/// This ensures the common config snippet stays up-to-date (e.g. when plugins like
+/// claude-hud update their cache paths between versions).
+pub(crate) fn sync_common_config_from_live(
+    db: &Database,
+    app_type: &AppType,
+) -> Result<(), AppError> {
+    let live_settings = read_live_settings(app_type.clone())?;
+
+    let snippet = crate::services::provider::ProviderService::extract_common_config_snippet_from_settings(
+        app_type.clone(),
+        &live_settings,
+    )?;
+
+    if !snippet.is_empty() && snippet != "{}" {
+        db.set_config_snippet(app_type.as_str(), Some(snippet))?;
+        log::info!(
+            "✓ Synced common config snippet for {} from live settings",
+            app_type.as_str()
+        );
+    }
+
+    Ok(())
+}
+
 pub(crate) fn strip_common_config_from_live_settings(
     db: &Database,
     app_type: &AppType,
diff --git a/src-tauri/src/services/provider/mod.rs b/src-tauri/src/services/provider/mod.rs
index f41fb78..e00d821 100644
--- a/src-tauri/src/services/provider/mod.rs
+++ b/src-tauri/src/services/provider/mod.rs
@@ -32,7 +32,8 @@ pub(crate) use live::sanitize_claude_settings_for_live;
 pub(crate) use live::{
     build_effective_settings_with_common_config, normalize_provider_common_config_for_storage,
     provider_exists_in_live_config, strip_common_config_from_live_settings,
-    sync_current_provider_for_app_to_live, write_live_with_common_config,
+    sync_common_config_from_live, sync_current_provider_for_app_to_live,
+    write_live_with_common_config,
 };
 
 // Internal re-exports
@@ -1721,6 +1722,18 @@ impl ProviderService {
             }
         }
 
+        // Sync common config from live settings before switching.
+        // This ensures the common config snippet in the DB stays up-to-date
+        // (e.g. when plugins like claude-hud update their cache paths).
+        if !app_type.is_additive_mode() {
+            if let Err(e) = sync_common_config_from_live(
+                state.db.as_ref(),
+                &app_type,
+            ) {
+                log::warn!("Failed to sync common config from live: {e}");
+            }
+        }
+
         // Additive mode apps skip setting is_current (no such concept)
         if !app_type.is_additive_mode() {
             // Update local settings (device-level, takes priority)

改动文件清单

文件 改动 行数
src-tauri/src/services/provider/live.rs 修复 provider_uses_common_config 推断默认值 + 新增 sync_common_config_from_live +44 -3
src-tauri/src/services/provider/mod.rs re-export 新函数 + 在 switch_normal 中调用 sync_common_config_from_live +15 -1
src-tauri/tests/provider_service.rs ⏳ 待补充(需 Rust 环境)

总计: 2 files changed, 55 insertions(+), 4 deletions(-)

@hengistchan
Copy link
Copy Markdown

wish it can fix as soon as possable🙏

@farion1231
Copy link
Copy Markdown
Owner

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 908ffc8744

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src-tauri/src/services/provider/live.rs
wait4xx added a commit to wait4xx/cc-switch that referenced this pull request Jun 5, 2026
When the live settings have no extractable common fields, the previous
code would silently skip the update, leaving stale entries in the DB.
With the new default in provider_uses_common_config, those stale fields
would be written back on the next provider switch.

Now we explicitly clear the snippet and set the cleared marker so that
provider_uses_common_config correctly returns false.

Addresses Codex review feedback on PR farion1231#3728.

Co-authored-by: GLM 5.1
…o-inference default

Three overlapping bugs cause statusLine, enabledPlugins, model, and other
common fields to be lost or overwritten with stale values when switching
Claude Code providers.

1. Fix provider_uses_common_config() default for exclusive-mode apps:
   - The json_is_subset heuristic was fundamentally broken because
     provider settings_config only contains vendor-specific fields,
     so the subset check always fails for providers without explicit
     commonConfigEnabled.
   - For exclusive-mode apps (Claude/Codex/Gemini), default to applying
     common config when a non-empty snippet exists.
   - Additive-mode apps keep the existing subset heuristic.

2. Add sync_common_config_from_live() to re-extract common config from
   the current live settings.json and persist it to the database on each
   provider switch. This ensures the common config snippet stays up-to-date
   (e.g. when plugins like claude-hud update their cache paths).

3. Call sync_common_config_from_live() in switch_normal() after backfill
   and before writing the target provider.

Co-authored-by: GLM 5.1

Closes farion1231#3631
wait4xx added a commit to wait4xx/cc-switch that referenced this pull request Jun 5, 2026
When the live settings have no extractable common fields, the previous
code would silently skip the update, leaving stale entries in the DB.
With the new default in provider_uses_common_config, those stale fields
would be written back on the next provider switch.

Now we explicitly clear the snippet and set the cleared marker so that
provider_uses_common_config correctly returns false.

Addresses Codex review feedback on PR farion1231#3728.

Co-authored-by: GLM 5.1
@wait4xx wait4xx force-pushed the fix/claude-preserve-common-config branch 2 times, most recently from 25fbcc4 to 2ce6805 Compare June 5, 2026 03:08
@wait4xx
Copy link
Copy Markdown
Author

wait4xx commented Jun 5, 2026

CI Update

Formatting issues have been fixed and all CI checks now pass on the fork:

  • cargo fmt --check
  • cargo clippy
  • cargo test

Verified on fork CI run: https://github.com/wait4xx/cc-switch/actions/runs/26994322690

Also addressed the Codex review feedback: when live extraction returns empty, the stale snippet is now explicitly cleared (with the cleared marker set) to prevent removed fields from being written back on the next provider switch.

Co-authored-by: GLM 5.1

When the live settings have no extractable common fields, the previous
code would silently skip the update, leaving stale entries in the DB.
With the new default in provider_uses_common_config, those stale fields
would be written back on the next provider switch.

Now we explicitly clear the snippet and set the cleared marker so that
provider_uses_common_config correctly returns false.

Addresses Codex review feedback on PR farion1231#3728.

Co-authored-by: GLM 5.1
@wait4xx wait4xx force-pushed the fix/claude-preserve-common-config branch from f434f0b to 0060472 Compare June 5, 2026 04:07
@farion1231
Copy link
Copy Markdown
Owner

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 00604723bd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src-tauri/src/services/provider/live.rs
Skip sync_common_config_from_live when the user has explicitly cleared
the common config snippet. This prevents re-enabling common config from
stale live fields after the user opted out.

Addresses Codex review feedback on PR farion1231#3728.

Co-authored-by: GLM 5.1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] 切换 Claude Code 配置时 settings.json 被全量重写,enabledPlugins / statusLine 等字段被清空或重置

3 participants