手册说能删、后端直接报错:当前供应商删除规则的两处矛盾
在 CC Switch 里删一个供应商,你可能会撞上一条互相打架的说明:用户手册 2.4 的「删除限制」小节写着「当前启用的供应商:可以删除,但建议先切换到其他供应商」,而 README 的 FAQ 里有一条标题直接是 “Why can’t I delete the currently active provider?”。后端源码给的是第二种答案——非累加模式的应用走到删除逻辑末尾会直接返回一个错误串。
三处的位置分别是:docs/user-manual/zh/2-providers/2.4-sort-duplicate.md:75、README.md:285、src-tauri/src/services/provider/mod.rs:2879-2890。以下所有内容基于我们本地 clone 的 cc-switch 仓库快照 c39c903(提交日期 2026-08-10),仓库内版本号 3.19.2,来源是源码与文档文本,我们没有安装也没有运行过这个桌面应用。
三处口径,各自的原文与位置
| 位置 | 原文要点 |
|---|---|
docs/user-manual/zh/2-providers/2.4-sort-duplicate.md:75 | 「当前启用的供应商:可以删除,但建议先切换到其他供应商」 |
README.md:285 | FAQ 条目标题 “Why can’t I delete the currently active provider?” |
src-tauri/src/services/provider/mod.rs:2879-2890 | 非累加模式下命中 current 即返回 AppError::Message("无法删除当前正在使用的供应商") |
这张表的读法:第一行是文档层的说法,第二、三行是 README 与代码层的说法,两边不一致。按我们的纪律,说完差异就停——不推断哪一处「才是对的」,也不猜为什么会这样,更不拿它去评价这个项目。往下有价值的是第二件事:这条规则并不是全局生效的,它按应用类型分叉,而这一点在上面三处里都没有被完整写出来。
反直觉的那一处:删除规则按应用类型分叉
ProviderService::delete 的入口在 src-tauri/src/services/provider/mod.rs:2831。它进来第一件事不是检查「这个供应商是不是当前项」,而是先问一句:这个应用是不是累加模式。
累加模式的定义在 src-tauri/src/app_config.rs:404-409 的 AppType::is_additive_mode(),返回 true 的只有 OpenCode | OpenClaw | Hermes 三个。累加模式的语义是:所有供应商共存于同一个 live 文件,没有「当前供应商」这个概念。既然没有当前项,也就无从谈起「不许删当前项」——删除规则会在这里分叉,根子就在这个方法的返回值上。
于是 delete 分成两条完全不同的路:
- 累加模式(OpenCode / OpenClaw / Hermes):可以随时删除任意供应商,删的时候同时把它从 live 配置里移除,再删数据库记录(
src-tauri/src/services/provider/mod.rs:2833-2878)。OpenCode 的omo/omo-slim两个分类还有专用分支,若被删的正好是当前那一项,会一并删掉对应的配置文件。 - 其余应用:同时检查本地 settings 里的 current 与数据库里的 current,任一等于待删的 id,就返回那个错误(
:2879-2890)。
代码侧 src/types.ts 的 VisibleApps 一共列了 8 个应用键:claude / claude-desktop / codex / gemini / grokbuild / opencode / openclaw / hermes。三个走累加模式,剩下五个走「不许删当前项」。所以手册那句「当前启用的供应商可以删除」在八个应用里有三个成立、五个不成立——差异的真实形状是这个,而不是简单的「文档写错了 / 代码拦住了」。
这条分叉里还藏着一个实现细节值得单独拎出来:累加模式判断「live 配置里到底有没有这一项」时,代码不信任那个标记位,而是用 check_live_config_exists 去实读文件。注释解释了原因:这个标记可能因为历史数据而 stale(:2861-2867)。至于哪些历史数据会让它失准、失准之后具体是什么取值,卡里没有记,我们不做推断。
删不掉的时候,怎么确认就是这条规则
先说判定动作,非累加模式的五个应用适用。
第一步,确认自己在哪个应用下操作。 如果你当前选中的是 OpenCode、OpenClaw 或 Hermes,那么删除失败一定不是这条规则拦的——那条路径根本不检查 current。剩下五个才走 current 检查。
第二步,理解它查的是两处而不是一处。 代码取的是本地 settings 的当前供应商与数据库的当前供应商两个来源,任一命中即报错。本地 settings 落在 ~/.cc-switch/settings.json(存的是设备级设置,注释写明它「不随数据库同步」,并注明「保留用于旧版本迁移和无数据库场景」);数据库是 ~/.cc-switch/cc-switch.db,供应商记录上有 is_current 标记。这两处在正常切换时是一起更新的:switch_normal 的第二步就是「非累加模式下更新本地 settings 的 current(设备级优先)与 DB 的 is_current(新设备默认)」(src-tauri/src/services/provider/mod.rs:3136-3143)。所以真正要核对的是:这两处指向的是不是同一个 id。
第三步,别把错误文案当判定依据。 这里要专门泼一盆冷水。代码层面,这几处错误的构造方式确实不一样:删除当前项走的是 AppError::Message("无法删除当前正在使用的供应商"),一个写死的中文串(:2879-2890);而切换被代理接管挡下走的是带 i18n key 的错误 switch.official_blocked_by_proxy(:3020-3031),自定义端点 URL 为空走的是 provider.endpoint.url_required(src-tauri/src/services/provider/endpoints.rs:34-54)。但这个差异不能拿来做判定:switch.official_blocked_by_proxy 在仓库里同样带着一句中文文案,也就是说「看到的是中文句子」并不能把这一类和那一类区分开。更要紧的是,我们没有安装、也没有运行过这个桌面应用,无法知道这两类错误最终在前端呈现成什么样,所以不据此下结论。判定的重心还是留在第二步的那两处 current 比对——那是你在本机文件与数据库里能自己查证的东西。
处置与验证。 文档语义给的处置就是手册那句话的后半段:先切换到其他供应商,再删。切换走的是 ProviderService::switch(:2966),非累加模式下会经 switch_normal 更新前面说的两处 current。验证方式是回到第二步——确认两处 current 都已经不再指向你要删的那个 id,再重试删除。
什么情况说明不是这个原因
这一节比上面几节更要紧,因为「删不掉」这个现象的成因不止一种。
如果你删的是统一供应商,走的是另一条函数。 delete_universal(src-tauri/src/services/provider/mod.rs:4621-4646)的顺序是:先删统一供应商本体,再按 apps 逐个删掉三个子供应商。子供应商的 id 规则是 universal-claude-{id} / universal-codex-{id} / universal-gemini-{id}(:4622-4645)。也就是说,删一个统一供应商是一次连带删除,它落在三个应用上,走的根本不是前面那条 current 检查的路径。排查时别把这两件事混成一件。
如果你想做的是「从 live 配置里拿掉、但保留这条记录」,那不是删除。 代码里另有 remove_from_live_config(:2892-2898),它只把供应商从 live 配置移除,不删数据库记录,供累加模式的应用使用。这两件事在语义上是分开的。
如果失败发生在切换而不是删除,也不是这条规则。 一个容易混淆的场景是代理接管开启时切到 official 分类的供应商,那里返回的是 switch.official_blocked_by_proxy,中文文案是「代理接管模式下不能切换到官方供应商,使用代理访问官方 API 可能导致账号被封禁」(:3020-3031)。这是切换路径上的限制,和删除无关。
如果你在累加模式应用下遇到删除失败,按前面的分叉,它不会是 current 检查——那条路径上会发生的事是从 live 配置移除与数据库删除,成因得另找,我们没有核实过那里的失败分支,不做推断。
README FAQ 给出的理由,以及它没说的部分
README 那条 FAQ 正文里给了设计上的理由:CC Switch 遵循「最小侵入」原则,即便卸载应用,CLI 工具也应继续正常工作;系统始终保留一份激活配置,因为删光所有配置会让对应的 CLI 不可用;不常用的 CLI 可以在设置里隐藏(README.md:285 起的 FAQ 段)。紧随其后的一条 FAQ 讲的是怎么切回官方登录——这一段我们只转述文档写了什么,不做任何延伸建议:在预设里添加官方供应商,切过去后执行一遍 Log out / Log in 流程;README 另称 CodeX 可在不同官方供应商之间切换,方便多个 Plus 或 Team 账号(README_ZH.md:295-297)。
需要提醒的是,这里被读写的不是 CC Switch 自己的私有数据。它操作的是 ~/.claude、~/.codex 这类真实的 CLI 配置文件,供应商配置里带着 API Key,这些都是你本机上的敏感数据。删除、切换、回填这几个动作都会落到这些文件上,动手之前先想清楚你在改的是哪一份文件——我们不对任何一种做法的安全性下结论。
收尾:这篇能带走的三件事
第一,「当前供应商能不能删」在这个仓库里有两种写法,位置分别是 docs/user-manual/zh/2-providers/2.4-sort-duplicate.md:75 与 src-tauri/src/services/provider/mod.rs:2879-2890(README FAQ 与后者同向)。你可以自己去这三行核对,我们只陈述差异。
第二,这条规则按应用类型分叉:AppType::is_additive_mode() 为真的 OpenCode / OpenClaw / Hermes 三个可以随时删,其余五个不行。判断自己属于哪一边,比记住结论更有用。
第三,删不掉时先确认自己在哪个应用下(累加与否决定走哪条路径),再看两处 current 是否还指向你要删的那个 id(~/.cc-switch/settings.json 与 ~/.cc-switch/cc-switch.db 里的 is_current),最后确认你删的是普通供应商还是统一供应商。这三步都落在你本机能查证的文件与记录上,能把「删不掉」这一个现象拆成几个不同的成因;至于错误提示长什么样,我们没有依据,不拿它当判定条件。
上面引用的行号对应的是 2026-08-10 我们读到的 c39c903 快照。这个项目仍在快速迭代,行号与分支结构随时可能变动,你自己 clone 之后要以当时的代码为准。
本文依据 CC Switch 官方仓库(github.com/farion1231/cc-switch)的 README、docs/ 下的用户手册与发布说明、
src/config/ 的预设定义与 src-tauri/src/ 的后端源码整理,核对日 2026-08-10,对应仓库快照 c39c903。
本文内容为仓库源码与文档口径,我们没有安装或运行过这个桌面应用,
因此不涉及界面外观、操作手感与切换速度的任何描述。
文中出现的阈值与默认值均为源码中的默认配置,不构成对实际运行结果的保证。
该项目仍在快速迭代,版本与默认值随时可能变动,请以仓库最新内容为准。
安全相关做法请结合自身环境评估,本文不构成安全方案建议。