[Bug] A degenerate streamed tool call (empty callId) is persisted, then the loader rejects it — entire session history becomes permanently unloadable #4387
alexzeng666
started this conversation in
General
Replies: 1 comment
|
落盘时空 dsh-session-surgeon 不会给空 callId 补假 id。可以 dsh plugin --profile web add "github:xiaoshenming/dsh-session-surgeon#main" |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
[Bug] A degenerate streamed tool call (empty callId) is persisted, then the
loader rejects it — entire session history becomes permanently unloadable
Environment: deepseek-harness 0.1.1-rc.2 (npx install), Windows 11,
adapter
@deepseek-ai/dsh-llm-pi-ai(api: openai-completions, gateway route).Summary
Known reports (#161, #725/#879/#885, #2979) cover the runtime side of this:
a streamed tool call whose identity fields get overwritten to
""fails withUNKNOWN_TOOL. This report is about what happens next: that invalidtool/result is happily persisted, and on reload
assertMessageEventShaperejects it, raising
SessionPersistenceCorruptionError("message must havetool source"). The whole session history is then unloadable, with no
quarantine, skip, or repair path. The writer accepts what its own reader
rejects.
Reproduction of the persisted state
Turn N, step S against an OpenAI-compatible gateway that repeats
id: ""on continuation deltas. The stored events:{"type":"assistant/message","seq":79431,"data":{"message":{"content":[ {"type":"reasoning","text":"…"},{"type":"text","text":"…"}, {"type":"tool-call","id":"","name":""}]}}} {"type":"tool/call","seq":79432,"data":{"callId":"","name":"", "arguments":"{\"file_path\":\"<path>\",\"limit\":\"10\",\"offset\":\"228\"}"}} {"type":"tool/result","seq":79433,"data":{"message":{ "source":{"kind":"tool","callId":""}, "content":[{"type":"tool-result","toolCallId":"","content":[ {"type":"text","text":"Error: unknown tool \"\""}],"isError":true}], "role":"user","id":"00e0c683-…"}}, "error":{"name":"ToolNotFoundError","code":"UNKNOWN_TOOL"}}}Loading the session afterwards:
The next request in the same run had already failed with the mirror error from
the provider (
messages[269]: missing field 'tool_call_id'), confirming theempty id round-trips into the LLM payload too.
Manual repair (worked)
Patching all four sites to one synthetic non-empty callId (and a placeholder
name derived from the arguments shape) makes the file load cleanly again —
no renumbering needed, so an official repair could be equally surgical:
{"type":"tool-call","id":""}→ set id/nametool/call.data.callId→ same idtool/result.data.message.source.callId+content[0].toolCallId→ same idSuggested fixes
packages/llm/llm/src/assembler.ts:70,still present as
partial.toolCallId = chunk.id;unguarded in@deepseek-ai/dsh-llm0.1.1-rc.2) — see An empty id/name in a later tool-call delta overwrites a good one: every call becomes unknown tool "" #2979.and mark the result as an error instead of writing a record the loader
will reject.
corruption fails loudly at write time.
dshrepair command or a quarantine policy for invalid events,so one bad event cannot brick an otherwise intact multi-MB history.
Related: #161 #725 #879 #885 #2979 (runtime cause); #1473, #3662 (persistence
corruption blast radius).
All reactions