You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
环境
0.2.2-rc.1(@deepseek-ai/dsh-desktop/@deepseek-ai/dsh-desktop-runtime),Windows 11 x64v24.21.0bt,modelqwen3.8-max,APIopenai-completionssession-a5389df2-c08c-4bbe-8541-01f0c613d689,cwd = F:\src\interactive-class,单 turn 内连续 73 个 step现象
该会话此后每次打开都会失败,报:
会话永久不可读、不可 resume,且 UI 没有修复或重扫入口。
根因(两层缺陷叠加)
1) 模型返回了完全为空的 tool call,DSH 写侧未拦截,直接落盘
seq 443 的
assistant/message是模型本次输出,其stream里工具调用块为空:{ "type": "chunk", "chunk": { "type": "tool-call-delta", "index": 1, "id": "", "argumentsDelta": "" } } { "type": "chunk", "chunk": { "type": "block-end", "index": 1, "block": { "type": "tool-call", "id": "", "name": "", "arguments": "{}" } } }同一条消息的
finish.reason仍是{ "kind": "tool-calls" },replayState.response.stopReason为toolUse,responseId: chatcmpl-96fcbe90-8f13-9731-899a-f36b75a373dc。DSH 随后把它原样写成 seq 444:
{ "type": "tool/call", "seq": 444, "data": { "turn": 1, "step": 73, "callId": "", "name": "", "arguments": "{}" } }注意
callId与name都是空字符串。校验器本身是知道这个不变量的——assertReleasedPayloadSemantics对tool/call明确要求nonEmptyString(data["callId"])、nonEmptyString(data["name"]),但实时追加路径没有执行该校验,于是坏行进了日志。这与 #7944 "V4 写侧 admission 不校验 tool-call 的 id/name" 的判断一致。2) 崩溃恢复为这个悬空调用合成 repair,而 repair 自身违反校验器 ⇒ 自锁
日志尾部只有"已通告、未启动"的 tool call,没有对应的
tool/result。恢复流程会为它合成一条TOOL_NOT_STARTED修复结果(interrupted-tool-result-<callId>-<seq>),但从空的 callId 派生,于是message.toolCallId === "",撞上assertV4ToolResultMessage的subject就是报错里的format v4 tool/result at seq 445。关键证据:磁盘文件中根本不存在 seq 445。文件最后一个事件是 seq 444,445 是恢复期在内存中合成出来的——也就是说恢复逻辑每次都在同一处生成一个自己拒绝接受的行,形成确定性死循环(与 #9731 "崩溃恢复每次都在同一 seq 自锁" 同因)。
帧级证据
文件
session.v4.jsonl.zstd= 473757 字节,zstd 多帧容器,共 252 帧:assistant/message@438,tool/call@439tool/result@440,step/end@441step/start@442assistant/message@443,tool/call@444(含空 id/name)损坏内容完整落在最后一帧内,与前 251 帧边界清晰。截断后事件数 443(header + seq 0–442),seq 连续无缺口。
本地修复(已验证)
在帧边界 472945 处截断到
step/start@442(保留部分是原文件的精确字节前缀),再用 DSH 自带的校验 worker 复核:verifyJsonlCurrentGeneration→decodeCurrentGeneration→validateStoredEvents→Session.fromRestore→ 嵌入流全量重放,结果ok: true,digest 299af7b6cd092c2037202c7320be527c6218776ed1af46cd9e1f5b67f8a0b464SessionFormatError: tool call id requires a nonempty string(Relationships.tool)数据损失仅为那条空工具调用本身(一句 reasoning,无回答文本)。这说明日志主体是完好的,坏的只是尾部一个不可执行的调用——完全可以通过一个宽容的恢复策略救回来,不需要用户手工剪帧。
建议修复方向
tool/call落盘前校验callId/name非空;更靠上游一点,在 provider 适配层(llm-pi-ai/ openai-completions 适配器)把"stopReason = toolUse但 tool call 缺 id/name"识别为协议错误,丢弃该调用并把本轮视为异常结束(或补一个确定性的合成 id),不要产生不可执行的悬空调用。当前行为是把上游协议异常写成了永久损坏。callId行做宽容归一化(例如派生确定性 idempty-tool-call-<seq>,并让 repair 复用同一 id),而不是合成一条被自己校验器拒绝的 repair。校验器与恢复器必须对"从不变量违例中恢复"达成一致,否则任何一条坏行都会变成不可恢复点。step/start后跟assistant/message(tool-call id="") +tool/call(callId="")且无 result,断言 (a) 写侧拒绝,(b) 若已存在则恢复可读。已知重复
同一缺陷在 Discussions 中已被多次报告,均未见修复,版本跨度
0.1.7-rc.2 → 0.2.2-rc.1:本贴补充的是
0.2.2-rc.1上的新证据:空 id 不仅来自 Anthropic/Messages 网关,也能来自 openai-completions 侧的btprovider;并且给出了"恢复期在文件中不存在的 seq 上合成自相矛盾 repair"的帧级证明与已验证的本地修复路径。建议把这一族问题合并处理,因为它会稳定地把任何一次上游协议异常升级为不可恢复的会话。All reactions