【免费下载链接】opencodex
Universal provider proxy for OpenAI Codex & Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code
导读
本文基于 opencodex 开发记录中一份真实落地的分支拆分方案(devlog/_fin/260722_claudedesktop_branch_split/000_plan.md),完整还原"把尚未成熟的 Claude Desktop 集成面从稳定的dev主干中拆出,同时把全部 Desktop 工作保留在直接堆叠在该主干之上的独立分支"这一 Git 历史重写操作。读者将掌握一套可复用的远程分支安全拆分范式:如何精确锁定拆分点 SHA、如何用--force-with-lease=refs/heads/<branch>:<expected-sha>实现"并发远端移动即中止"的原子保护、如何设计可验证的产物边界,以及如何在出错时无副作用的回滚。文中所有命令与验证步骤都结合了当前仓库源码与测试给出实操依据。
背景:为什么要把 Claude Desktop 从dev主干中拆出去
opencodex 是面向 OpenAI Codex 与 Claude Code 的通用 provider 代理,Claude Desktop 是其"Claude 生态集成"工作面的重要一环。仓库中与 Claude Desktop 相关的实现横跨多个模块,从 CLI 到 GUI、再到服务端管理面,例如:
- gui/src/pages/ClaudeDesktop.tsx —— Desktop 配置页,管理 opus/fable/sonnet/haiku 四个模型家族的路由分配、默认模型与 apply 指纹;
- gui/src/pages/integrations/overview-clients.ts —— 集成总览中把
claudeDesktop作为与 codex、claude、grok、cursor 并列的顶层集成客户; - src/cli/claude-desktop.ts ——
ocx claude desktop子命令族,提供 apply/show/status/move/default/export/import; - src/claude/desktop-first-party.ts(
--first-party模式)、src/claude/desktop-3p.ts(--gateway模式)、src/claude/desktop-remote-store.ts(远端配置存储); - src/claude/desktop-policy.ts —— Windows 机器级策略(
HKLM\SOFTWARE\Policies\Claude)的只读、隐私安全探测。
2026-07-22 这次拆分发生时,Claude Desktop 集成面仍处于"未成熟"状态,不应进入dev的日常演进;但整套 Desktop 工作又不能丢失。于是需要一次"远程dev历史重写(Work class: C4)":让dev回到不含 Desktop 的干净基线,把包含 Desktop 的完整历史原样保存到新分支claudedesktop,并且保证新分支直接堆叠在dev之上,未来可以干净地合并回主干。
工作类别说明:
C4表示"remotedevhistory rewrite",即对远端dev分支的历史进行重写。这类操作在 devlog 中被视为常规(但高风险)的开发簿记,拆分的最终结果(分支结构、SHA)已经存在于公开 git 历史中,因此这份计划属于_fin/下的"已关闭记录"。
拆分点锁定:精确到 SHA 的基线计算
任何历史重写的第一步都是把"干净基线"与"Desktop 集成分支"之间的边界精确锁定。该文档给出了四个关键 SHA:
| 含义 | SHA |
|---|---|
当前本地/远程dev(含 Desktop 与两笔修复) | 6da54a8965c24cdd31c4641ac3a864aad4968204 |
| Desktop 合并提交(作为被剔除的合并) | 418d29b1a0e7ae4945648720bd1bda390caf12ba |
干净的dev基线(合并的第一父提交) | 79e5067a4058f6c7e44462dbb188707fa6df70d6 |
| Desktop 功能分支父提交 | 0c092de3ab54d78e87d0d84dad1c5826b1ef636c |
其推导逻辑非常值得借鉴。文档指出:当前dev在 Desktop 合并之后只有两笔提交,且两笔都是 Desktop/i18n 集成的修复:
ed2ed675—— 清理 Desktop 分支引入的冲突标记残留行;6da54a89—— 为日语(ja)、俄语(ru)补充 Claude Desktop 相关键。
因此"不包含 Desktop 的干净dev"在数学上恰好等于 Desktop 合并提交的第一父提交:
干净 dev = 418d29b1^1 = 79e5067a这一行推导是整个拆分方案的基石:只要合并提交的两个父提交(第一父 = 合并前主干,第二父 = 被合并分支)语义正确,就能用"取第一父"的方式无损剥离合并内容,得到恰好等于"从未合入 Desktop"的主干状态。
实现侧佐证:Claude Desktop 的 GUI 界面在 gui/src/pages/ClaudeDesktop.tsx 中单独成页,且集成总览把它作为独立客户端建模(见 gui/src/pages/integrations/overview-clients.ts)。这正是文档中"以
gui/src/pages/ClaudeDesktop.tsx是否存在于某分支来判定 Desktop 是否被引入"这一内容边界能成立的原因——Desktop 是一个可独立成页、可整体判定存在与否的功能面。
执行窗口与前置条件:写冻结与并发安全
该计划在正式执行前明确了一个关键的运行约束——"短写冻结窗口":
- 仅有一个本地 worktree;
- 没有其他本地
dev检出; - 在重写完成并验证通过之前,不允许任何其他 agent 向
dev推送。
这个约束的动机是重写后的"陈旧检出"问题:任何基于6da54a89的过期 checkout,在下次推送前必须先 reset 或 rebase,否则一次普通的 fast-forward 推送就可能把 Desktop 内容"意外放回"dev。换句话说,历史重写的真正风险不在重写本身,而在重写之后其他人基于旧 SHA 的常规推送。
正式执行前的六项前置条件(在真正变更前一刻必须全部成立,否则中止):
- worktree 干净;
- 本地
dev == origin/dev == 6da54a89(本地与远端一致,无漂移); - 本地与远程均不存在
claudedesktop分支(确保新建分支不会意外覆盖或复用它); origin只包含main、preview、dev三个分支;- 对预期产物做一次预演确认(见下文"执行步骤");
- 再次确认写冻结窗口内没有其他推送者。
执行步骤:先备份、再移动、最后重写
整个执行是一个"先建备份、再动主干、最后验证"的六步流程,每一步都在可逆与不可逆之间划出清晰界线。
Step 1 — 拉取并修剪远端引用,且仅当所有前置条件在变更前一刻仍成立时继续。前置条件的意义是"基于当前快照的承诺":一旦条件变化(例如远端多了新分支、本地出现脏文件),整个流程立即失败,避免基于过期信息做破坏性操作。
Step 2 — 在本地创建claudedesktop分支,指向当前dev的6da54a89:
git branch claudedesktop 6da54a8965c24cdd31c4641ac3a864aad4968204这一步只是本地书签,不触碰远端,也不移动dev。
Step 3 — 先建立远程备份,且使用"不存在的引用 + lease"双重保护:
git push --force-with-lease=refs/heads/claudedesktop: \ origin refs/heads/claudedesktop:refs/heads/claudedesktop这里的--force-with-lease=refs/heads/claudedesktop:(期望 SHA 为空)意味着"只有当远端该引用不存在时才允许创建",从而杜绝意外覆盖一个已经存在的同名分支。推送后立即验证远程claudedesktop == 6da54a89;一旦不符,中止且绝不移动dev。
Step 4 — 仅在重新确认 worktree 干净之后,把检出的本地dev移动到79e5067a:
git reset --hard 79e5067a4058f6c7e44462dbb188707fa6df70d6此时本地dev已经"回到"不含 Desktop 的基线,但远端dev尚未被触碰——本地与远端暂时分叉,这个窗口内绝不推送。
Step 5 — 重写origin/dev,并用"预期旧值"锁死并发:
git push --force-with-lease=refs/heads/dev:6da54a8965c24cdd31c4641ac3a864aad4968204 \ origin 79e5067a4058f6c7e44462dbb188707fa6df70d6:refs/heads/dev这是整个方案的技术核心。--force-with-lease=refs/heads/dev:6da54a89的含义是:只有在推送时远端dev仍精确等于6da54a89(即仍然是我们推导出干净基线的那份历史)才执行强制更新;如果在我们执行期间有任何并发推送移动了dev,本次推送会被 Git 拒绝而非覆盖对方的工作——"并发远端移动即中止,而不是被覆盖"。
Step 6 — 完整验证。见下一节。
验证矩阵:让"成功"可以机械判定
重写完成后,计划定义了一套可逐条机械判定的验证矩阵:
| 检查项 | 期望结果 |
|---|---|
本地与远程dev | 均为79e5067a |
本地与远程claudedesktop | 均为6da54a89 |
git merge-base --is-ancestor dev claudedesktop | 成功(dev是claudedesktop的祖先) |
origin分支集合 | 恰好为main、preview、dev、claudedesktop(加上HEAD) |
| 内容边界 | gui/src/pages/ClaudeDesktop.tsx在dev上不存在、在claudedesktop上存在 |
其中git merge-base --is-ancestor dev claudedesktop的"祖先关系"验证尤其重要:它证明的不是"两个分支碰巧内容相似",而是claudedesktop确实完整包含dev的全部历史,因此未来主干演进之后,claudedesktop可以在不产生重复合并、不丢失主干提交的前提下被干净地合回。这正对应文档"分支直接堆叠在该dev基线之上"的目标。
边界佐证:
ClaudeDesktop.tsx之所以能作为"Desktop 是否引入"的判定锚点,是因为该页面在 GUI 中是 Claude Desktop 唯一专属的顶层页面,且集成总览对claudeDesktop的行模型(见 gui/src/pages/integrations/overview-clients.ts)使得它"要么整体在、要么整体不在"。
边界约束与回滚预案
计划明确列出了操作的边界,防止重写过程中顺手做无关变更:
- 不做任何源码编辑(这是纯 git 簿记操作);
- 不移动
main/preview; - 不发布 release、不执行 npm publish;
- 不创建或删除其他远程分支。
**回滚预案(Rollback)**同样是一套可机械执行的反向序列:
git fetch --prune;- 断言远端
origin/dev == 79e5067a、本地claudedesktop == 6da54a89(只有期望状态才允许回滚); - 用带 lease 的推送恢复远端
dev:
git push --force-with-lease=refs/heads/dev:79e5067a4058f6c7e44462dbb188707fa6df70d6 \ origin refs/heads/claudedesktop:refs/heads/dev—— 即"用claudedesktop分支把dev推回6da54a89",且同样以79e5067a作为 lease 期望值,防止覆盖并发移动; 4. 验证远端dev == 6da54a89; 5. 断言本地 worktree 完全干净; 6. 只有以上全部通过后,才把本地devreset 回精确的6da54a89; 7.claudedesktop作为备份保留,除非用户另行授权删除。
回滚设计的核心思想:先恢复远端、再恢复本地。因为在重写过程中本地与远端短暂分叉,若先动本地会丢失唯一可信的恢复源(claudedesktop上保存的完整 Desktop 历史)。
拆分结果与质量验证
按计划执行后的最终状态:
dev(本地/远程):79e5067a4058f6c7e44462dbb188707fa6df70d6—— 稳定主干回到不含 Claude Desktop 的干净基线;claudedesktop(本地/远程):6da54a8965c24cdd31c4641ac3a864aad4968204—— 完整 Desktop 工作原样保留;main与preview:不变,仍为6d6bef8b98d762ff9679916546cb44e8e3effebc;- origin 头部分支:恰好为
claudedesktop、dev、main、preview; - 祖先关系:
dev是claudedesktop的祖先; - 内容边界:
ClaudeDesktop.tsx在dev上缺席、在claudedesktop上存在; - worktree:干净,本地
dev与origin/dev一致。
重写之后还附带了三重质量验证,证明"历史变了但代码没坏":
bun run typecheck通过;- 隔离的测试套件完整运行且无失败退出;
- 与重写后精确 SHA
79e5067a完全一致的既有 Cross-platform CI 运行29914489485为绿色——这得益于"干净基线 = 合并第一父提交"的特性:79e5067a原本就是历史中的真实提交,CI 早已为它跑通过。
可复用的方法论提炼
从这份计划中可以提炼出一套适用于任何"从主干剥离不成熟功能面"场景的通用流程:
- 用合并拓扑锁定边界:若待剥离内容是一次合并引入的,且其后只有少量修复提交,则"干净基线 = 合并提交的第一父提交",且验证"合并后提交集全部属于该功能"即可无损推导;
- 先备份后重写:任何历史重写都必须先把"被剥离的内容"完整保存到一个可恢复的分支(本地 + 远程双备份),再动主干;
- 给每个强推带上精确 lease:
--force-with-lease=refs/heads/<branch>:<expected-old-sha>把"我知道远端此刻是什么"显式写成承诺,任何并发移动都会让强推安全失败而不是静默覆盖——这正是本方案能在多人协作仓库中放心执行的根本原因; - 用祖先关系代替内容比较:
git merge-base --is-ancestor证明新分支完整包含主干历史,为未来干净合并背书; - 回滚 = 反向重写:回滚本身也是一次带 lease 的强推,且遵循"先远端后本地"的顺序;
- 善用 CI 历史:因为拆出的基线是历史真实提交,可以复用该 SHA 上既有的绿色 CI 运行作为重写后的质量证据,而不必等待全量重跑。
对于 opencodex 的后续演进,claudedesktop分支在 Desktop 面成熟后可以沿dev的祖先关系干净合并回主干;在合并之前,dev保持不受 Claude Desktop 集成面干扰,让主干演进与实验性集成互不阻塞——这正是本次分支拆分的最终目的。
【免费下载链接】opencodex
Universal provider proxy for OpenAI Codex & Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code
相关推荐
深入解析 Git Remotes 历史改写:force push 的危险、force-with-lease 安全推送与协作最佳实践
深入解析 Git Remotes 历史改写:force push 的危险、force with lease 安全推送与协作最佳实践 本篇文章以 The Odin
文档教程教育opencodex 分支合并实战:claudecode → dev 的 53 提交安全合流与冲突解决记录
opencodex 分支合并实战:claudecode → dev 的 53 提交安全合流与冲突解决记录 本文基于 opencodex 仓库 devlog/_f
tldraw 仓库实战:用 clean-copy 技能把混乱分支重写成叙事级提交历史
tldraw 仓库实战:用 clean copy 技能把混乱分支重写成叙事级提交历史 导读 本指南以 tldraw 仓库内置的 Agent 技能文档 skill
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考