v3→v4 read-time migration refuses sessions with unresolved tool calls inside closed steps (damage from #7228's crash window) #7617
Replies: 5 comments
|
We maintain plugins for this harness and we have been reading the The decision record already covers the shape you propose
The section that matters here is
The shape you verified — six synthetic rows inserted into a released v3 artifact, with Cause ownership is deliberate, not incidental
Why the repair cannot see closed steps: it is the cursor, not a ruleTwo places in
So "only an open tail" is not a separate policy check that could be relaxed. The pending-call map is emptied at every step boundary, and the function returns empty whenever the last turn is already closed. Damage whose step has closed is invisible by construction. The same structure exists in The migration side is a codec
The same transcript shape, a different gate
Offered as an observationIf the record's position holds, the direction with the fewest open policy questions is a read-only recovery path: the v3 file is intact and the refusal is non-destructive, so a tool could read the log, apply the closers in memory, and write a new artifact or an export, leaving the original file exactly as the read path found it. We are not asking for a timeline or a specific design, and we have not reproduced your session — the reading above is from published packages ( Authorship note: this reading is mine, against published package contents and the repository's own decision record; root-cause reading and drafting assisted by AI; verification and publication by me. I have no engineering background — if any technical claim reads wrong, please call it out; I will re-verify against the published packages and correct. We are plugin authors, not maintainers, and nothing here states official policy. Reported by the OfferKuai Team. Contact: contact@offerkuai.com · GitHub: @yamingmou 中文版我们维护这个 harness 的插件,做迁移这块工作的同时也一直在读仓库里的 你要做的那个形态,决策记录里已经有立场
而和这里最相关的是
(译文:未来若要做「恢复部分崩溃工作」的功能,应当设计成面向用户的显式恢复视图,而不是把合成事件静默插进规范 transcript。) 你实测出来的形态 —— 往一个已成版的 v3 档里插入六条合成行,并同步挪动 cause 的归属是刻意的,不是偶然的
为什么修复看不到已闭合的 step:是游标,不是规则
所以「只修 open tail」不是一条可以放宽的策略判据,而是每个 step 边界都会清空待办表、且末轮一旦闭合整个函数就直接返回空。落在已闭合 step 里的损坏,从结构上就看不见。 同样的结构在 迁移这一侧是 codec
同一损坏形状,另一道门
作为一条观察,而非请求如果决策记录的那条立场仍然有效,那么政策疑问最少的方向是一条只读的恢复路径:v3 原档完好、拒绝也是非破坏性的,所以一个工具可以读出日志、在内存里套用 closers,然后写出一个新的产物(或导出件),让原档保持读路径发现它时的原样。我们不要求时间表、也不指定具体设计;并且我们没有复现你的会话 —— 以上读数来自已发布包( 声明:本次通读与根因追查由我本人完成(对象为已发布包内容与仓库决策记录);文稿撰写由 AI 辅助;核验与发布由我本人负责。我没有工程背景 —— 若任何技术表述有误,请直接指出,我会对照已发布包重新核实并更正。我们是插件作者、不是维护者,本文不陈述任何官方政策。 本报告由 OfferKuai(Offer快)团队提交。 联系:contact@offerkuai.com · GitHub:@yamingmou |
|
One more ripple worth noting: while the session was unloadable, the app also dropped it from its workspace's |
|
A second store, and one consequence that is easy to lose inside the refusal itself. What you describe is a disagreement between two stores. The log is the one that gets repaired; the workspace membership is the one that keeps the old state, and nothing on the opening path mentions it. So a refused session is not an unchanged session: one of the two was rewritten while the other was still refusing, and the second write left no message of its own. That is the part we would keep next to a repair step, rather than inside it: after the log opens again, the membership is a separate thing to re-read, because a session can be present in the log and absent from its project at the same time. Your note also shows the two failure modes are independent — the grouping did not come back with the history, and it took a second, explicit change to bring it back. We are reporting the shape of it rather than asking for anything; we have not reproduced it ourselves. Authorship note: the observation restated above is another reporter's, on a version of Reported by the OfferKuai Team — Founder: Zhaofeng (Yaming). Website: https://www.offerkuai.com/ | Contact: contact@offerkuai.com 中文版这是第二个 store,以及一个容易在"拒收"本身里被忽略掉的后果。 你描述的是两个 store 之间的分歧:日志是被修的那一个,工作区成员表是留着旧状态的那一个,而打开会话的整条路上没有任何一处会提到它。所以一次被拒的会话不等于一个没被改动的会话 —— 两者之一在另一个还在拒收的时候已经被改写了,而那次改写自己没有留下任何消息。 这一点我们倾向放在修复步骤旁边、而不是里面:日志重新能打开之后,成员表是另一件要单独重读的事,因为一个会话完全可以同时"在日志里存在"而"不在它原本的项目里"。你这条也顺带说明这两种失效是相互独立的 —— 分组并没有随历史一起回来,它需要第二次、显式的改动才回来。 我们报告的是这个形状本身,不是提请任何改动;我们这边没有复现。 声明:上列观察转述自原帖报告者,其读数出自原帖已注明的 本报告由 OfferKuai(Offer快)团队提交 —— 创始人:Zhaofeng(Yaming)。官网:https://www.offerkuai.com/ | 联系:contact@offerkuai.com |
|
One measurement that belongs next to this refusal, because it decides whether such a session can open at all. The v4 relationship assertion is not only reached through publish. Identical on One detail that may help attribution: the read path wraps the format error as Runnable probe (five rows: both migration stages accept, both restores refuse, a stored current-generation read refuses; exits non-zero if any row drifts): https://github.com/xiaoshenming/dsh-session-surgeon/blob/1bf58a3/fixtures/probes/run.mjs Unrelated to any tool: your second-store observation is the part we would keep, too — a refused session is not an unchanged session, and the membership store keeps no message of its own. |
|
Noted on the measurement, and the wrapper point carries over cleanly: the class names the entry point, so a report that quotes the outer sentence already answers "which path refused" without anyone reading code. One note that may help a later reader, and it is about navigation rather than about the reading. This assertion now has two independent arrivals in the record — the one here, and the same measurement in #4549 (#4549), where the same shape was run through the real v3→v4 stage and read back. Someone who lands on one of the two has no way to know the other exists, and the two together are what make the fifth row look like a property of the assertion rather than of a particular caller. On the second store: we have nothing to add beyond what we wrote here on 09-24, and we would keep the same half you kept — a refused session is not an unchanged session, and the membership store keeps no message of its own, so it has to be read again after the log is repaired rather than inferred from the repair. We say that as a restatement of the earlier note, not as a new finding. Authorship note: the reading behind this reply is mine, against published package contents; drafting assisted by AI; verification and publication by me. I have no engineering background — if any technical claim reads wrong, please call it out; I will re-verify against the published packages and correct. Reported by the OfferKuai Team — Founder: Zhaofeng (Yaming). Website: https://www.offerkuai.com/ | Contact: contact@offerkuai.com 中文版测量这条收到;而包装器那一点可以直接沿用:错误类是入口的标签,所以一份引了外层那句话的报告,不必有人去读代码,就已经回答了"是哪条路径拒的"。 再给一条给后来读者的注记 —— 它关于导航,不关于读数本身。这道断言在记录里现在有两次独立到达:这里这一次,以及 关于第二个 store:除了我们 09-24 在这里写过的那一句,补不了别的;而我们保留的正是你保留的那一半 —— 被拒的会话不是没变过的会话,成员存储自己不携带任何消息,所以它必须在日志修好之后被重新读一次,而不是从修复动作里推断出来。这一句是对先前那一条的重申,不是新发现。 声明:本回复所依据的判读由我本人对照已发布的包内容完成;文稿撰写由 AI 辅助;核验与发布由我本人负责。我没有工程背景 —— 若任何技术表述有误,请直接指出,我会对照已发布的包重新核实并更正。 本报告由 OfferKuai(Offer快)团队提交 —— 创始人:Zhaofeng(Yaming)。官网:https://www.offerkuai.com/ | 联系:contact@offerkuai.com |
Uh oh!
There was an error while loading. Please reload this page.
Summary
Follow-up to #7228 (the src/lib dual-module bug whose first symptom was every tool call failing with
Cannot read properties of undefined (reading 'prepare')). That bug's crash window — between thetool/callappend and thetool/resultappend inagent-loop'srunGroup— left durable damage in sessions it hit. After upgrading past the v4 transition, any such session now fails to load entirely:The raw v3 log is intact on disk (the non-destructive refusal works as documented), but there is no self-healing path, so the session — including all of its healthy earlier turns — is unreachable from the UI.
Why the existing repair does not cover it
openTurnClosers(@deepseek-ai/dsh-session/repair) deliberately repairs only an open tail: "Calls in already closed steps remain unchanged, including any missing results." The #7228 crash wrotestep/endandturn/end(reasonerror) after the danglingtool/call, so the damage sits inside closed steps:tool/callrow, the rest never did;tool/result;assertV4LifecycleRelationshipsrefuses at the firststep/endthat follows (closeTools).One real-world session produced six unresolved calls across four turns this way (four started, two advertised-but-never-started from one parallel batch).
A repair shape the current validator already accepts
For recovery purposes I confirmed the v4 relationships validator already accepts synthetic closers for both cases, inserted between the dangling
tool/call(or theassistant/message, for never-started calls) and the step'sstep/end:tool/result(role: "user", onetool-resultwrapper withisError: true, inner text fromCLOSER_TEXT.interrupted.started), envelopesourceEventSeqs: [<tool/call seq>], noerrorfield. Accepted as a started call's normal result.CLOSER_TEXT.interrupted.notStartedas the inner text,data.error = { name: 'ToolNotStartedError', code: 'TOOL_NOT_STARTED' }, nosourceEventSeqs, message idinterrupted-tool-result-<callId>-<digits>— matchingnotStartedRepairexactly. Note the v3 canonical-payload rule requireserrorrows to carry thetool-resultwrapper withisError: true(the bare first-class text shape is rejected byassertCanonicalPayload; the validator checks the lifted inner content).With those six rows inserted (and subsequent seqs/reference fields shifted —
sourceEventSeqs,surfaceOp.startSeq/endSeq, theremapV3Referencesfield set), the session migrates cleanly:MIGRATION OK (1796 v4 events)throughcreateSessionFormatCatalogWithChildren(...).createRestore(header, { recovery: 'recoverable', validation: 'transformed' }).Suggestion
The v3→v4 transform could synthesize exactly these closers when a
step/end/turn/endwould otherwise refuse on unresolved calls — mirroringopenTurnCloserssemantics for closed steps (TOOL_OUTCOME_UNKNOWNfor started calls citing the call seq,TOOL_NOT_STARTEDfor advertised-but-never-started ones). That keeps the refuse-without-rewriting default while giving read-time migration a self-heal for crash damage that exists in the wild. Alternatively, a session-doctor path (as proposed in #1518) could apply the same repair explicitly.The v3 generation on disk was never touched by the refusal; the repair above was applied to a user-consented copy with a backup, and the session loads normally again.
All reactions