前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
返回 AI 情报前线
All News · 全部资讯8732
  • Cloudflare Worker Previews:为 Agent 每次变更提供隔离预览环境
  • NVIDIA Isaac ROS 5.0 发布:加速 Agentic 机器人开源开发
  • Meta Muse macOS 应用零日漏洞遭修复
  • 小米6天烧2000万训练开源模型MiMo,跃居开放模型榜首
  • LLM应用输入输出Guardrails实战指南
  • Claude发现libheif堆缓冲区溢出:HEIC图片供应链安全研究
  • 多模型调度与反馈闭环:基元律动CTO谈Agent持续进化
  • 阮一峰科技周刊第413期:再见了React Native
  • 用Python/ChromaDB/Gemini搭建轻量级RAG实战
  • 生产环境中降低LLM成本的七种实战方法
  • AI驱动开发反思:过度规格化反而拖累AI编程
  • AMD ROCm:AI Agent正在降低GPU编程门槛
  • 小米MiMo-V2.6-Pro登顶开源权重模型榜首,300万美元预训练
  • 四Agent并行:状态文件是保持一致性的关键
  • 长任务前何时压缩上下文:Claude Code实战经验
  • 5美元/月自建Qwen2.5 72B推理服务:vLLM+AWQ量化部署指南
  • Prompt缓存失效的根因:工作流在悄悄破坏精确前缀匹配
  • NVIDIA SoL-Pi:编码Agent的Token流量降低49%的Auto-Research框架
  • Grok 4.7 发布:更大基座、同价位,编程与 Agent 任务升级
  • vibe coding 两小时后质量崩塌的根因分析
  • 用 git 在多设备间同步 Claude Code 和 Codex 会话
  • 小米 MiMo-V2.6-Pro 开源:科研任务效率提升 10 倍
  • 评估Agent代码补丁前先清点测试面
  • 阿里云栖大会:Qwen4训练中,全模态模型新版本亮相
  • Qwen4.5后参数扩展至5-10T,Qwen4和视频模型均在训练
  • 阿里云栖大会正式发布Qwen4
  • 凌晨3点LLM路由崩溃排查:上游回收引发的级联故障
  • Hugging Face Transformers现已支持运行llama.cpp量化模型
  • Jev:按输入计费的决策模型,输出免费
  • Langflow OSS 认证 RCE 漏洞 CVE-2026-17633
  • 小米 MiMo-V2.6 开源:AA 指数最高开源模型
  • xAI发布Grok 4.7:编程强化模型,百万词元2美元起
  • Meta AI 助手 Muse 存在关键 0-day 漏洞,ClickFix 攻击可劫持
  • AWS发布Strands Harness:开源Agent框架,Token成本降28%
  • Spec-Driven 开发:摆脱 Vibe Coding 的工程化实践
  • AI 编程助手 CSS 好看但上生产就崩的根因与修复
  • Agent 诊断结果上线前如何验证:分离决策与执行
  • OpenAI 成立数学顾问组,AI 已解决逾百开放难题
  • Grok Build vs Claude Code:记忆能力实战对比测评
  • AI编码导致CI成瓶颈?我们重新设计了CI流程
  • AI Agent 在无需文字的决策上浪费大量 Token
  • Grok 4.7长时运行失败率仍高,编程Agent可靠性堪忧
  • 在自有硬件上运行前沿 AI 模型
  • 从氛围记忆到确定性自主:AI 原生基础设施设计思路
  • 用 Agent 流程开发简单 SPA 的实战经验
  • AI 生成 React UI 的真实问题:从第二页开始组件漂移
  • vLLM 深度解析:高吞吐 LLM 推理的性能瓶颈
  • xAI Grok 4.6 登陆 Amazon Bedrock
  • AWS 开源 AI 编程助手,成本比 Claude Code 低 45%
  • 生产级 AI 工作流:构建 Prompt 测试框架实战
  • Google 确认 Gemini 模型今年5月入侵三家公司
  • 已加载 51 / 8732
8.0
热点
AI SCORE
编程提效2026-09-22 11:58

用 git 在多设备间同步 Claude Code 和 Codex 会话

dev.to · AI#Claude Code#git#多设备协作
Editor brief · 编辑速览

通过一个私有 git 仓库(vault)同步 AI 编程会话,配合 --resume 命令在换设备后无缝继续工作,避免会话困在单一机器。

文章思维导图
Knowledge map
拖拽缩放
Full translation

完整中文译文

我同时用三台笔记本电脑工作。Claude Code 和 Codex 都把每次对话存在启动它的那台机器上——这意味着那个终于让 AI 理解了代码库的会话,只能困在你上周二坐的那个地方。

Copilot 的解决方案是账户级云同步。但我不想让自己的代码对话日志存在厂商的云端,也不想跑一台服务器或为 S3 付费。我日常已经在用、信任且拥有的基础设施,是一个 git remote。于是我做了 repo-sessions:会话通过一个私有 git repo(一个"保险库")同步,同步触发器就搭在你已经在做的 git push 和 pull 之上。在笔记本 A 上 push,在笔记本 B 上 pull,然后 claude --resume,对话就在那里了。

这篇文章讲的是整个构建过程,主要通过三个 bug 来讲。每一个看起来都是小的边缘情况,最后都被证明是结构性的问题。

为什么不能直接复制文件

Claude Code 把每个会话存为 JSONL 文件,放在 ~/.claude/projects/<munged-path>/<session-uuid>.jsonl 下。munged path 是你项目的绝对路径,把所有 [A-Za-z0-9-] 之外的字符都替换成短横线:

export function mungeCurrent(absPath: string): string {
  return absPath.replace(/[^A-Za-z0-9-]/g, '-');
}

所以 /Users/rishi/dev/proj 变成 -Users-rishi-dev-proj。在此基础上,转录文件的每一行都嵌入了你的 cwd、会话 id、工具版本和 git 分支。

两个后果。第一,resume 是通过你所在目录的 munged path 来查找会话的,所以在另一台机器上(不同的用户名、不同的克隆路径)文件在一个 Claude Code 永远算不出来的目录下。第二,即使你把文件放到了正确位置,嵌入的路径指向的也是那台机器上不存在的目录。

在写任何正式代码之前我通过实验验证了所有这些,因为这些都没有文档。方法是:启动一个建立了暗号的会话,手工把转录文件复制到第二个位置并重写路径,然后尝试 resume。对照组先跑:在"B 机"上不重写就 resume 结果是"No conversation found",这正是这个工具要修复的失败方式。重写后,resume 回来的会话回忆起了暗号,并报告了 B 机的路径作为它的 cwd。

这次探索还暴露了一个令人不舒服的事实:munge 规则在 Claude Code 各版本之间已经漂移了。我这台机器上同一个目录的转录文件同时存在于 -AI-AI-ML 和 -AI-AI_ML 之下,说明某个旧版本保留了下滑线。所以定位器改为扫描所有已知的规则变体,而不是计算一个目录名然后信任它。当你在未文档化的格式上构建时,你就继承了它们的历史。

在 push 时,转录文件中的所有绝对路径都变成 token:${CSS_PROJECT_ROOT}、${CSS_HOME},以及 munged 后的 dirname(当工具输出涉及 ~/.claude/projects/ 时就会出现)。在 pull 时,token 会被重写为本地机器的路径。Codex 需要第三种路径表面,因为它的 rollout 文件包含对 $CODEX_HOME 的自引用。

这个保险库刻意做得很无聊:一个私有 git repo,普通目录,每个项目一个命名空间,以规范化后的 origin URL 的哈希为 key。任何 git UI 都能检查它。同步触发器是 git hooks(pre-push 发出会话,post-merge 和 post-checkout 接收它们),已有的 hooks 会被链式加载,stdin 和 argv 保持完整,所以你的 husky 配置继续工作,它的失败仍然会中止 git 操作。我的从来不会:如果保险库不可达,一切表现得就像这个工具根本不存在一样。

Bug 一:JSON 里的 Windows 反斜杠

在实际的 Windows 笔记本上 dogfooding 时发现的症状:一个同步过来的会话能打开,但只有最后几轮对话渲染出来了。

原因:转录文件是 JSON,而我在往原始字节里做路径替换。把 C:\Users\rishi\dev\proj 拼接到 JSON 字符串字面量里,你就写出了 \U,这不是一个合法的转义序列:

{"command":"ls C:\Users\rishi\dev\proj"}     <- 无法解析的行
{"command":"ls C:\\Users\\rishi\\dev\proj"} <- 应该在磁盘上的样子

Claude Code 解析不了这些损坏的行,连接各轮对话的 parent-uuid 链断裂了,UI 只显示那些幸存下来的内容。所以现在所有转录文件的替换都工作在 JSON 转义后的拼写上:tokenizer 匹配路径的每种拼写形式(转义的、正斜杠的、任意大小写驱动器的、最长的优先,这样转义形式会在较短的之前被消费掉),重hydration 时发出转义形式。

同一家,同一周:Windows 的 git 的 autocrlf 把 LF 的保险库文件在 checkout 时改成了 CRLF,导致同步的记忆文件里出现了幻内容变更(修复:vault 里加 * -text 加上 EOL 不敏感的对比)。还有 git 默认的 1 MiB http.postBuffer 在通过 https push 超过几兆字节的转录文件时回以一个不透明的 HTTP 400,导致后台 push 静默失败——对于一个同步工具来说,这是最糟糕的失败模式。

目前的教训是:同一份内容在跨机器时比你想象的拥有更多字节表示形式。我以为我已经学会了。下一个 bug 是带着牙的同一课。

Bug 二:那个引用了自己同步元数据的会话

一个月后,一个会话在每次 pull 时都被判定为永久分歧,两台机器都是,即使我知道什么都没变。不用说,那正是我用 Claude 调试这个工具的会话。

机制是这样的。Tokenization 是机器相对的:我的 Mac 的 tokenizer 认识 Mac 的路径。但这份额 transcript 把我的 Windows 机器的路径拼写作为普通消息文本引用了——粘贴的终端输出、像 C--Users-rishi-... 这样的 munged dirname、一段元数据文件。当 Mac push 时,这些字符串原封不动地进入了 vault,因为对 Mac 来说它们不是路径,只是文本。Windows 机器的 tokenizer 本可以折叠它们。所以没有任何 Mac 能执行的 tokenization 能复现 vault 的字节内容,哈希永远对不上,同步层得出结论:会话已经分歧了。

然后事情变得更糟了。reconcile 命令在字节级别找两份副本的公共前缀。第一个引用拼写出现在第 62 行左右,所以它在那里找到了一个假的分叉点,并把之后的所有内容作为"分歧尾"拼接回去。每次修复命令运行都会让转录文件膨胀:471 行,然后 881 行,然后 1701 行,有些对话轮次被重复了八次。看着一个修复命令把病人越治越糟,是一种特殊的体验。

修复分两部分。每次 push 现在都在 vault index 里记录那个设备的路径上下文(项目根目录、home、工具数据目录)。比较操作不再碰原始字节:两端都在 vault 见过的每种设备上下文下通过 tokenizer 做折叠,按确定性顺序。

export function canonForCompare(adapter: Adapter, content: string, ctxs: PathCtx[], opts?: SubstOpts): string {
  let out = adapter.stripVolatile ? adapter.stripVolatile(content) : content;
  for (const c of ctxs) out = adapter.tokenize(out, c, opts);
  return out;
}

Token 在进一步 tokenization 下是惰性的,所以这个折叠是一个投影,执行两次等于执行一次。这个属性是一条来自事故现场的单元测试:

const once = canonForCompare(claudeAdapter, fromA, ctxs, { json: true });
const twice = canonForCompare(claudeAdapter, once, ctxs, { json: true });
expect(once).not.toBe(fromA); // 折叠确实折叠了外来拼写
expect(twice).toBe(once);     // 并且它是一个投影

同样重要的是:正规形式只是用于比较。Vault 始终存储 writer 自己的 tokenization,安装时始终重 hydration 原始存储字节。比较逻辑可以独立演进,而无需重写任何人的数据。

Bug 三:工具在你背后编辑了转录文件

几周后,虚假的分歧又回来了,这一次转录文件里根本没有引用任何外来路径。

原来 Claude Code 大约每轮对话就在当前活着的机器上追加一条 title-refresh 记录({"type":"ai-title",...})。同一对话的两份诚实副本最终会拥有不同数量、不同位置的这类记录。从内容上说,一份是另一份的干净前缀,只是落后了一点。从字节上说,分歧了。

所以正规形式多了第二条轴:在路径折叠之前,adapter 可以剥离工具非确定性重写的记录。剥离是解析确认的,只丢弃顶级 JSON type 为 ai-title 的行,所以把这样一条记录作为数据引用的对话轮次会保留下来。这立刻就派上了用场,因为那个调试会话不断引用它们。

一个更普遍的教训比任何一个 bug 都更沉重:比较正规形式必须规范化每一条良性非确定性的轴,而且厂商每次发版推出的新记录类型都可能重新引入这个 bug 类。当你在未文档化的格式上构建时,你就在这个跑步机上。我的妥协是一个 doctor 命令,通过唯一不会说谎的方式验证 drift:实际 resume 一个会话并检查工具的响应。

存活下来的规则

三条原则完整地走过了这一切,现在成为每个功能的门槛:

Transcript 是只追加的。Prefix 意味着快进,分歧意味着冲突副本,chat rebase 把尾部的尾巴拼接回一个转录文件("与此同时,在另一台笔记本上",这是真相)。没有任何东西会被静默丢弃。Rewind 是从 vault 历史的一个分支,从不原地截断,因为其他设备会把"丢失的"轮次直接 push 回来。被删除的会话从 vault 历史恢复,因为 vault 是 git,每个同步状态都是一次提交。

永远不要写另一个工具的数据库。Codex 在 rollout 文件旁边的 SQLite 里索引会话。我不碰它。把重写过的 rollout 放入日期树,首次 resume 时索引会自愈。稍微差一点的 UX(交互式选择器只在按 id resume 过一次后才列出同步的会话),但失败模式好得多。

降级到无形。Vault 不可达、工具未设置、repo 未启用:每个 hook 都安静退出,coding 工具的行为完全就像 repo-sessions 从未安装过一样。

现在长什么样

npm i -g repo-sessions
chat setup   # 每台机器一次:创建或复用你的私有 vault
chat init    # 每个 repo 一次:从这里开始的 git push/pull 携带你的会话

然后正常干活。会话和每个项目的记忆跟着 repo 走。chat list、chat resume <name>、当你在两台机器上同时超前时用 chat rebase,当你删了不该删的东西时用 chat restore。

(二进制文件名叫 chat。macOS 附带了一个拨号时代的老 pppd 工具在 /usr/sbin/chat,在安装前打开的 shell 会愉快地运行那个版本,它会阻塞在 stdin 上,看起来完全就像"工具什么都不做"。hash -r。别问我怎么知道的。)

MIT 许可,TypeScript,零运行时依赖,61 个密封测试跑在 3-OS CI 矩阵上,每天在三台机器上 dogfooding,包括一个 400k token 的会话跨 OS 准确保留了 recap 恢复过来。一个真实的警告:转录文件包含你会话看到的一切。Vault 必须是私有的(setup 拒绝公开 GitHub vault),push 跑一个 gitleaks 风格的 secret 扫描,还有一个 per-session 的忽略文件,但你应该像对待 .env 一样对待 vault。

我会告诉你偷什么

字节相等对于同步内容来说是错误的身份。你需要对每个 writer 的编码都成立的相等,而且你应该预期 writer 集合会增长。

如果同步内容可以把你自己的同步元数据反引用回来,它最终会的。任何关于这个工具本身的会话都是一个自引用的输入,而 dogfooding 保证这种会话必然存在。

在比较时规范化,从不静止规范化。只用于比较的正规形式可以自由演进,因为它从不重写存储的数据。

当没有文档时,一个暗号和一个阴性对照在一个晚上能搞定推测永远搞不定的事情。

Repo: https://github.com/firish/repo-sessions

如果你尝试了,我没找到的那些失败模式是我最想听到的。

Original source

本文由 AI 翻译整理自 dev.to · AI,原文版权归原作者所有。

阅读英文原文
上一篇
vibe coding 两小时后质量崩塌的根因分析
下一篇
小米 MiMo-V2.6-Pro 开源:科研任务效率提升 10 倍