[BUG] 会话日志损坏:关机中断后 seq 序号冲突——"中断收尾"事件与真实事件的 seq 撞号,导致会话无法加载 #2342
Replies: 8 comments 1 reply
|
你的根因定位是对的,而且这是会话损坏家族的第 7 个机制——一个我追踪已久但一直没等到现场证据的缺口:冷加载修复路径缺少 liveness 检查。我验证了完整链路。 1. 机制确认(master 47f9438)
2. 家族连接 这是第 7 个机制: 3. 修复方向(按价值排序)
4. 你的报告质量很高——字节级证据(时间戳复用 + 撞号 + 文件后续连贯)+ 手工修复 + 明确定位,直接把这个"冷路径无 liveness 检查"从我的待验证清单变成了家族第 7 个实证。建议合并进统一的"replay/append 所有权契约"加固工作流(#1333/#1452/#1497/#1473/#1586/#2167 + 本帖)。 |
|
根因确认:这是「合成尾部事件」( 止血/抢救两条: npx dsh-shelf verify # 标出 orphan tool call / 没 turn/end 的坏会话
npx dsh-shelf rescue <session-id> # 无视 resume 校验,解码(含 zstd)导出对话内容到 markdown
另外这个家族的检测我已经做进 dsh-plugin-doctor v1.16.0( |
|
This is the missing half — my comment stopped at "synthetic tail collides with a real event"; you've traced it to the actual defect: the repair path ( The family framing is the key insight: #1333/#1452 (dup seq), #1497 (torn tail), #1473 (corruption), #1586 (restore-writer vs live-writer), #2167 (duplicate append), #2342 (repair-writer vs live-writer) — all the same contract: append/repair has no "who owns the log" check. Agree with the fix order:
I'll fold your root-cause + fix order into the dsh-docs patch queue (this is a 3-patch family: repair-liveness, seq-ownership, reader-self-heal). If you have a branch already, point me at it and I'll base off it instead of re-deriving. |
|
Landed fix #1 (repair-path liveness) as a cherry-pick-ready branch:
The change re-checks One thing I intentionally left as a note rather than claiming: the throw-vs-retry interaction with the coordinator-contract suite — Fixes #2 (seq-ownership) and #3 (reader self-heal) are still analysis-only in the patch queue. |
|
Landed fix #1 (repair-path liveness) as a cherry-pick-ready branch:
The change re-checks One thing I intentionally left as a note rather than claiming: the throw-vs-retry interaction with the coordinator-contract suite — Fixes #2 (seq-ownership) and #3 (reader self-heal) are still analysis-only in the patch queue. |
|
Update: I ran the (sqlite suite is 99/100 — the one failure is a Windows So fix #1 is now fully verified on my side. Remaining: #2 (seq-ownership) and #3 (reader self-heal). |
|
One more finding while I was in the code — fix #2 (seq-ownership) is already substantially implemented, so it may be narrower than the family framing suggests:
So the remaining gap is not "append/repair has no seq check" but specifically: Do you have a specific repro where the revision guard does not change but a writer still collides? If so, that's the exact place fix #2 should add a seq assertion; otherwise I'd fold #2 into a defensive assertion in |
|
Fix #3 (reader self-heal) is also landed:
So the #2342 family now has two landed patches (#1 liveness, #3 reader self-heal) and #2 narrowed to a defensive seq assertion in |
Uh oh!
There was an error while loading. Please reload this page.
环境
dsh-session-persistence-jsonl,zstd 压缩现象
关机重启(进程被强杀)后,有两个会话无法加载,报错如下:
从日志字节中定位到的根因
两个日志里都出现了同一批 seq 序号被使用两次的情况:一次是"回合中断"的合成收尾事件(
step/end+turn/end {reason: interrupted},时间戳复用了上一条事件的),另一次是真实事件(assistant/chunk/tool/result+step/end+step/start,时间戳更晚)。且文件之后继续连贯——说明真实写入方当时还活着、还在继续追加,而加载侧的修复逻辑同时注入了收尾事件:SessionLogScanner.consumeEventLine里对 seq 连续性做了严格校验(event.seq !== this.events.length),撞号后直接拒绝加载整个会话。两个文件末尾都是以turn/end (completed)正常结束的,所以这个冲突是运行中途由"加载时修复"与"正在写入的进程"竞态造成的,而不是最后一次关机瞬间写坏的。期望行为
撕裂尾部的恢复逻辑(
commitRepair重放恢复的事件 + 追加合成收尾)不应与并发写入方产生 seq 撞号;理想情况下,如果后续真实事件证明该回合其实并未中断,加载器应当能通过丢弃合成收尾事件来恢复会话。相关讨论:#483(强杀进程后 write-behind 批处理丢失未刷新的尾部数据)、#1593(会话日志损坏导致会话永久不可用)。
补充说明:我的两个会话已通过本地脚本修复(扫描定位 seq 撞号 → 删除合成收尾行 → 按原格式重新编码),验证可正常加载,此贴不是求助帖,是报告底层缺陷。附修复思路供参考:这类"合成收尾事件与真实事件撞号"的损坏,可通过比对事件类型和时间戳(合成收尾时间戳复用上一条事件、且为 step/end + turn/end interrupted)识别并删除合成行恢复;但手动编辑日志有风险,且不覆盖"缺号"等其他损坏形态,建议官方实现自愈逻辑(丢弃合成收尾、或修复时与写入方串行化)。
All reactions