Skip to content

ccswitch "应用 <agent> 配置" 写入策略是"整段追加",导致 YAML duplicate 键报错

🕒 Published at:

ccswitch "应用 <agent> 配置" 写入策略是"整段追加",导致 YAML duplicate 键报错 ​

一份给 ccswitch 维护者的 issue 草稿。适用于任何被 ccswitch 管理的、配置文件是 YAML 格式的 agent / runtime(claude / codex / gemini 之外的第三方应用,例如 hermes / openclaw 等)。


环境 ​

  • ccswitch (Rust 编译的 Tauri 桌面应用,托盘常驻)
  • 任意受 ccswitch 管理的第三方 agent / runtime(下文以 &lt;agent&gt; 代替)
  • 配置文件: ~/{app 配置目录}/config.yaml
  • 操作系统: Windows 11(在 macOS / Linux 应同样可复现,因为问题在 ccswitch 的 Rust 代码,不在 OS 行为)

现象 ​

启动 &lt;agent&gt; 时报:

配置错误: Failed to parse &lt;agent&gt; config as YAML: duplicate entry with key "..."

... 可能是 custom_providers / model / mcp_servers 三者之一,也可能其他顶层键。

config.yaml 里出现重复的顶层键。

复现步骤 ​

  1. 正常使用 &lt;agent&gt;,config.yaml 是 &lt;agent&gt; 自己的完整配置(包含 agent / terminal / display / voice / security / cron / kanban / gateway / 各种 auxiliary provider 等数百个字段)
  2. 在 ccswitch 托盘菜单里点击 &lt;agent&gt; 的"切换到 <某个供应商>"(或 ccswitch 自动检测到 model 指针指向不存在的供应商,自动重建)
  3. 触发"应用 <agent>" 动作
  4. 再次启动 &lt;agent&gt; → YAML 报 duplicate

根因 ​

ccswitch 的 "update <agent> model defaults after switching to '<provider>'" 函数(疑似位于 cc_switch_lib::services::provider 模块)实现是:

从 ccswitch 自己的数据库(cc-switch.db 的 providers 表)读出"它认为完整的 &lt;agent&gt; 配置" → 整段附加到 &lt;agent&gt; 的 config.yaml 末尾

这份"完整配置"至少包含三个顶层键:

  • model: (当前激活的 default + provider)
  • custom_providers: (所有 &lt;agent&gt; 供应商列表)
  • mcp_servers: (ccswitch 还会顺便带一份 mcp server 配置)

ccswitch 从来不做 in-place 合并,只会"追加整段"。所以:

情况结果
目标文件已有 model: 块追加后 → duplicate model
目标文件已有 custom_providers 段追加后 → duplicate custom_providers
目标文件已有 mcp_servers 段追加后 → duplicate mcp_servers
目标文件没有这些键看起来"成功",但 ccswitch 自己的小文件覆盖了 &lt;agent&gt; 的完整配置

旁证:ccswitch 对 &lt;agent&gt; 是"半吊子"管理 ​

(以一个第三方 agent 为例,以下是通用现象)

  • cc-switch.db 的 proxy_config 表有 claude / codex / gemini 的代理配置,不一定有 &lt;agent&gt;
  • proxy_live_backup 表只对 claude / codex 有备份,不一定有 &lt;agent&gt;
  • ccswitch 日志里大量 [Claude] 和 [Codex] 的请求记录,很少或没有 [&lt;Agent&gt;]
  • settings.json 里 currentProvider&lt;Agent&gt; 字段可能缺失
  • providers 表里 &lt;agent&gt; 供应商的 is_current 字段长期为 0

也就是说 ccswitch 在 UI 上声称管理 &lt;agent&gt;,但实现上不一定有装网络代理、不一定标 is_current、不一定有 live takeover 机制,而 "update <agent> model defaults" 函数又用了粗暴的"追加"策略。

日志特征(对排查有用) ​

  • 写入时不写 INFO 日志,只在失败时打 WARN,例如:
    [WARN][cc_switch_lib::services::provider]
    Failed to update &lt;agent&gt; model defaults after switching to '&lt;provider&gt;':
    配置错误: Failed to parse &lt;agent&gt; config as YAML: duplicate entry with key "custom_providers"
  • 写入"成功"时完全静默,无任何日志输出
  • ccswitch 每次"应用"动作会在 ~/.cc-switch/backups/&lt;app_type&gt;/ 创建一份 &lt;app_type&gt;_&lt;时间戳&gt;.yaml 时间戳备份

建议的修复方向 ​

按"根治性"递减,任选其一即可:

  1. 改 in-place 合并:读取文件 → 解析 YAML → 改 model 块的 default / provider 字段 → 写回。不要追加。
  2. 写入前做 YAML 解析校验:用 serde_yaml / yaml-rust 解析写入后的文件,确保无重复键、无 schema 错误。失败就回滚 + 报错。
  3. 写入前用锁文件机制:例如 ~/.{app}/.cc-switch.lock,避免和 &lt;agent&gt; 自己的写入并发。
  4. 如果决定保留"整体重写"策略:
    • 完整重写 &lt;agent&gt; config(不是追加),保留 &lt;agent&gt; 自己的所有字段(不光是 ccswitch 知道的 N 个供应商)
    • 在重写前备份用户原始完整配置(不只备份 ccswitch 写的"应用前"版本)
  5. 如果决定保留"追加"策略(最不推荐):
    • 至少做去重:写入前先去掉 model / custom_providers / mcp_servers 这几个顶层键的旧值,再追加
  6. 降低日志噪音:写入"成功"也写一条 INFO,方便用户 / 开发者知道 ccswitch 对 &lt;agent&gt; 做了什么

临时绕过(给遇到同样问题的用户) ​

如果不想等修复,退出 ccswitch 进程 或把 settings.json 里 "visibleApps": { "&lt;agent&gt;": false } 设为 false 可以彻底规避 —— 但代价是 ccswitch 不能再帮你管理 &lt;agent&gt; 供应商。

也可以把 &lt;agent&gt; 的 config.yaml 重命名成 config.yaml.bak(让原位置空)—— 这时 ccswitch 会自己创建一个小文件,在它上面"切换"是 in-place 编辑、不重复 —— 但这个文件只包含 ccswitch 关心的字段,&lt;agent&gt; 的所有个性化设置会丢。

给其他用户的自查清单 ​

如果你也遇到类似 duplicate 报错,可以按下面顺序自查:

  1. 看 ~/.cc-switch/logs/cc-switch.log 最近 50 行,搜 Failed to update &lt;agent&gt; —— 有日志就是中招
  2. 看 ~/.cc-switch/backups/&lt;app_type&gt;/ 是否有最近 24 小时内的时间戳备份 —— 有就说明 ccswitch 在写
  3. 打开 config.yaml 找 model: / custom_providers: / mcp_servers: 这几个键是不是出现两次
  4. 临时绕过:退出 ccswitch 进程,问题立刻消失