深度解析)
cc-switch 本地路由服务proxy_enabled 开关持久化与停止时配置覆写修复#7210深度解析【免费下载链接】cc-switchA cross-platform desktop All-in-One assistant for Claude Code, Codex, OpenCode, OpenClaw, Grok Build Hermes Agent. Only official website: ccswitch.io项目地址: https://gitcode.com/GitHub_Trending/cc/cc-switch本文拆解 cc-switch 本地路由服务本地 HTTP 代理默认监听127.0.0.1:15721的一次生命周期变更停止代理时不再把 Claude 行的重试与超时设置覆写到 Codex、Gemini 与 Grok Build 三个应用的代理配置上。依据是 CHANGELOG.md 中 v3.20.3 的 #7210 变更记录以及 server.rs、services/proxy.rs、dao/proxy.rs 的源码。读完后你将掌握proxy_enabled开关如何让关了就是关了、启停生命周期如何收敛以及旧实现为何会覆写、新实现为何不再覆写。TL;DR改了什么正常退出与临时端口分配两条路径不再触碰其他应用的每应用代理设置修复对应 issue#7210。为什么改旧的全局代理配置写入器执行的是无WHERE子句的UPDATE把 Claude 的max_retries与三个超时字段抄到了所有应用行。影响谁同时启用多应用代理的用户只跑单应用者无感知。你要做什么升级后在代理设置里核对一次 Codex/Gemini/Grok Build 的重试与超时把被改过的项重置回你的值。下面从一个真实现象说起看我没改的设置自己变了是怎么发生的。场景重现为什么我没改的 Codex 设置自己变了你给 Codex 配了max_retries 5和一组超时值日常用 Claude 的代理跑得很顺。某天你正常退出 cc-switch下次再打开发现 Codex 那套重试与超时已经被换成了 Claude 的值。你没碰过 Codex设置却自己变了。更隐蔽的是那次停止代理本身是成功的——端口确实关了、状态确实回到未运行。问题出在停止动作顺手改写了别的应用的行。对开关不生效排查来说这类操作成功了但别处被污染的故障最难定位因为报错点和你看到异常点不在同一处。旧实现为什么不行一个没有 WHERE 子句的写入器症状每次正常退出Codex、Gemini、Grok Build 三行的max_retries与三个超时字段都被替换成 Claude 的值临时端口分配路径同样触发。根因停止时代理的恢复路径调用了一个沿用多年的全局代理配置写入器本意是清除早已废弃的live_takeover_active标记而该写入器读取的是 Claude 行并执行一条不带WHERE子句的UPDATE于是把读取值写进了整张表。修复删掉这条死写入把解析出的临时端口改走全局配置接口——该接口只触碰共享的监听与日志列不碰每应用字段。关键在死写入这个词那条UPDATE从未真正携带它想清除的标记除了把 Claude 的值抄给所有人之外没有任何作用。删掉它等于移除一个只制造副作用、不产生收益的调用。新机制如何运作理解了覆写如何发生再看新的启停与持久化机制如何保证关闭即生效、且不误伤他人。开关的唯一来源proxy_enabled 持久化列一句话结论是否启动路由唯一依据是数据库里的proxy_enabled列运行时状态不反向改写它。在 schema.rs 中该列默认关闭proxy_enabled INTEGER NOT NULL DEFAULT 0, listen_address TEXT NOT NULL DEFAULT 127.0.0.1,dao/proxy.rs 负责读写这一组列proxy_enabled、listen_address、listen_port、enable_logging。这代表你看到的开关状态和落库的意图是同一份数据不存在界面关了、库里还开着的第二份状态。启停两个入口start() 与 stop() 的幂等一句话结论start()重复调用会直接拒绝stop()对未运行服务也明确报错两个入口都靠共享句柄判断真实状态。server.rs 中start()先查关闭通道句柄pub async fn start(self) - ResultProxyServerInfo, ProxyError { // 检查是否已在运行 if self.shutdown_tx.read().await.is_some() { return Err(ProxyError::AlreadyRunning); }这代表启动与停止都以是否持有运行句柄为准而不是依赖某个可能滞后的布尔标志避免以为没跑其实跑了的竞态。退出时的配置隔离只碰共享列一句话结论停止与端口分配现在只写共享的监听/日志列每应用的重试与超时不再被触及。#7210 的修复把两条路径统一到只动共享列的约束下。变更记录明确写道删除死写入后live-config 恢复、备份清理与健康重置保持不变回归测试为四个应用各置一套互不相同的设置断言两条路径都存活。这代表停止代理这一动作的副作用边界被收窄——它只负责关自己不再顺手改邻居。兜底策略stop() 的 5 秒超时保护一句话结论停止会等待服务任务真正结束但最多 5 秒超时即强制继续不让退出被卡死。server.rs 的stop()用tokio::time::timeout包住等待逻辑超时返回StopTimeout。这代表即便某个连接迟迟不释放关闭流程也能返回保证关了就是关了不因个别慢连接而悬空。新旧行为对照维度旧行为新行为受影响人群正常退出覆写 Codex/Gemini/Grok Build 的重试与超时仅写共享监听/日志列多应用用户临时端口分配同样触发覆写改走全局配置接口端口被占用需换端口的场景关闭的live_takeover_active靠死UPDATE残留死写入被删除全部每应用设置可能被 Claude 行污染各自独立保持全部谁受影响只跑单应用代理的用户完全不用动行为与升级前一致。同时用多应用代理的高级用户升级后核对一次各应用的重试与超时把被改过的重置回原值——已覆写的值无法自动恢复。发布与运维本次不变更数据库 schema版本仍为 18需向用户说明历史覆写不可逆需手动重置一次。用户操作指南开关不生效排查与配置重置停止代理主界面顶部代理开关点为白色或设置 → 高级 → 代理服务关闭开关。预期面板显示未运行端口15721不再监听。验证对http://127.0.0.1:15721/health发请求应连接失败。核对每应用设置进入各应用的代理设置检查max_retries与三个超时字段。预期Codex/Gemini/Grok Build 的值是你自己配的而非 Claude 的值。验证与升级前记录比对。重置被污染的值把不一致的项改回你的目标值并保存。预期保存后数据库对应行更新为你设置的值。验证重启代理后再次读取仍为所设值。验证与保障仓库为该变更配套了分层证据可作实现事实的印证回归测试变更记录记载为四个应用各置一套互不相同的每应用设置断言正常退出与临时端口分配两条路径下这些设置都存活——直接锁定 #7210 的覆写不再发生。生命周期单测server.rs 内置#[cfg(test)]用例覆盖启动/停止与独立端点派生验证运行态由句柄而非标志位驱动。持久化契约services/proxy.rs 中启动置proxy_enabled true、停止置false的意图写入保证开关状态跨重启保持避免运行时停了、库里还开着的漂移。附录配置速查与延伸阅读项值 / 说明默认监听地址127.0.0.10.0.0.0允许局域网默认端口15721端口占用需先停止代理再改开关列proxy_enabled默认0关闭相关列listen_address、listen_port、enable_logging健康检查GET /health修改监听地址或端口前必须先停止代理被历史版本覆写的每应用重试/超时值不会自动恢复需手动重置一次。延伸阅读相对路径docs/user-manual/zh/4-proxy/4.1-service.md代理服务启动/停止、配置与请求流程docs/user-manual/zh/4-proxy/4.2-routing.md应用路由原理与配置恢复src-tauri/src/proxy/server.rsstart()/stop()生命周期与内置测试src-tauri/src/services/proxy.rsproxy_enabled意图持久化CHANGELOG.mdv3.20.3 #7210 完整变更与升级说明【免费下载链接】cc-switchA cross-platform desktop All-in-One assistant for Claude Code, Codex, OpenCode, OpenClaw, Grok Build Hermes Agent. Only official website: ccswitch.io项目地址: https://gitcode.com/GitHub_Trending/cc/cc-switch创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考