fix(claude): resume 预检跨桶兜底,修复 /cd 空窗重启后静默丢上下文#506
Open
xu4wang wants to merge 1 commit into
Open
Conversation
xu4wang
force-pushed
the
fix/resume-cross-bucket
branch
from
July 17, 2026 03:55
6defe99 to
44d2671
Compare
Claude 的 transcript 按 cwd 分桶存,/cd 只改未来写入落点、不回迁已有 文件。若 /cd 后没写任何 turn 就重启,transcript 孤儿在旧 cwd 桶,而 checkResumeTargetExists 只探当前 cwd 桶 → 判 false → worker 丢弃 --resume、静默新开会话,整段上下文丢失。 修复:<sid>.jsonl 在同一 dataDir 下全局唯一。当前桶探不到时扫兄弟桶, 命中则把孤儿 transcript 迁进当前桶(claude --resume 只认当前 cwd 桶, 迁移是唯一能让它找到文件的方式)后返回 true。要点: - 移动而非拷贝:旧桶留 stale 副本会在日后 /cd 回旧目录时被直接命中、 静默丢掉其后的 turn;rename 失败(EXDEV/权限)降级 copy 后同样尝试 unlink 源文件,删不掉打 stderr 告警。 - 多桶命中(claude 自身 /cd 后重写留下的旧副本)取 mtime 最新。 - sid 先过 UUID 校验才允许动文件(防路径穿越)。 - 全败时回滚自建的目标桶目录 + probe 侧预先快照 projectDir 存在性, 保证原有 false(provably absent → 干净降级 fresh)/undefined 语义 一字不漂移。 - 迁移成功/unlink 失败均有 stderr 日志可排障。 已验证:12 个单测(含 bug 复现、copy 降级、全败回滚、穿越拒绝); 真机端到端两轮——含最坏场景(工具飞行中 kill -9 留下悬空 tool_use 尾部)跨桶迁移后 claude --resume 均完整续回上下文。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- codex review 加固(2026-07-17)--- - 符号链接逃逸:扫描改用 lstatSync(不跟随),并对胜出者加 realpath 包含性校验, 杜绝经植入软链把 dataDir 外文件迁进桶当 transcript 读。 - sidecar 随迁:move `<sid>.jsonl` 的同时迁移相邻 `<sid>/` 目录(tool-results 等), 否则 resume 后转录里对工具结果的引用取不到。尽力而为、失败不阻断 rescue(jsonl 已就位)。 - 新增 3 个单测:sidecar 随迁、sidecar 缺失不报错、软链逃逸被拒。 - partial-cpSync 兜底:sidecar 拷贝中途失败时清掉半成品目标,避免 existsSync 守卫让后续 rescue 永久跳过(sidecar 永久分裂)。
xu4wang
force-pushed
the
fix/resume-cross-bucket
branch
from
July 17, 2026 03:59
44d2671 to
228a04c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
问题
Claude 家族 CLI 的 transcript 按 cwd 分桶存(
<dataDir>/projects/<slug(realpath(cwd))>/<sid>.jsonl),且/cd只改未来写入的落点、不回迁已有文件。于是存在一个竞态:会话/cd之后、写下任何 turn 之前发生重启(挂起后续起 / daemon 重启 / crash),旧 transcript 孤儿在旧 cwd 的桶里,而checkResumeTargetExists只探当前 cwd 的桶 → 判false("provably absent")→ worker 丢弃--resume、静默新开会话,用户看到:整段上下文丢失,且历史文件其实还完好地躺在磁盘上(另一个桶里)。现网已两次踩中(角色切换后的空窗重启是最常见触发路径)。
修法
<sid>.jsonl在同一 dataDir 下全局唯一。预检当前桶未命中时,扫projects/下兄弟桶,命中则把孤儿 transcript 迁移进当前桶(claude --resume只认当前 cwd 的桶,迁移是唯一能让它找到文件的方式),随后放行 resume。要点:/cd回旧目录时被预检直接命中、resume 到过期上下文,静默丢掉其后的所有 turn(正是本修复要防的形态)。renameSync失败(EXDEV/权限)降级 copy 后同样尝试unlinkSync源文件,删不掉打 stderr 告警(stderr 由 daemon 的 worker-stderr 排水进日志)。/cd后重写会在旧桶留副本)。false(干净降级 fresh)/undefined(可能 mid-session 轮换,交给 secondary guard)的分支判定一字不变。只在 miss 路径触发(每次 spawn 至多一次),每桶一次
statSync,满足接口 "synchronous, cheap" 的要求。测试
test/claude-resume-cross-bucket.test.ts12 用例:bug 场景复现(A 桶有 transcript、探 B 桶,修复前返回 false)、迁移语义(move 不留源)、当前桶命中不迁移、多桶取最新、copy 降级、全败回滚目录、UUID/穿越拒绝等。12 passed (12)。vc-meeting-daemon-session、v3-distillation-*、v2-run-archive;浮动项单跑均过,属并发 flaky)。checkResumeTargetExists探 dirB(触发迁移,返回 true,文件从 A 桶移入 B 桶)→ dirBclaude --resume正确答出暗号。kill -9claude,transcript 尾部悬空tool_use→ 跨桶迁移到 dirB →claude --resume依然完整续回上下文并答出暗号。关联
与 xu4wang:fix/role-switch-respawn(角色切换改 respawn+resume)配套:该 PR 的 respawn 在新 cwd 探桶,硬依赖本修复接住旧桶 transcript。本 PR 可独立合并(对现有 /cd 冷启动路径本身就是修复);对方 PR 必须晚于或与本 PR 同时合并。
🤖 Generated with Claude Code