Replies: 1 comment
|
Follow-up evidence for Trigger B: same-instant cross-session batch splice collisions (2026-09-04, ~4 h after the original report) A re-scan of all 48 session logs on this machine found 5 newly corrupted logs within ~40 minutes, every one with the identical minimal signature: exactly one duplicated event per log, and the duplicated row is
The first four collided within a 2 ms window (21:38:38.362-.364 local) across two different workspaces: the memory plugin (openviking) delivered an updated user-profile context to every session at once ( All 5 logs were repaired losslessly by dropping exactly the 1 colliding splice event per log (everything else kept byte-for-byte, no seq renumbering, so Takeaway: Trigger B is user-reachable at scale - a single batch delivery of the memory plugin corrupted 4 sessions across 2 workspaces in the same instant. The append-time seq guard (fix 2) or the single seq allocator (fix 3) would have prevented all 5. |
Uh oh!
There was an error while loading. Please reload this page.
session: committed-region seq collisions from divergent next-seq cursors — sessions become permanently unobservable; error misreports as "torn JSONL record"
Observed on 0.1.2-alpha.4 (Trigger A) and re-confirmed on 0.1.2-rc.1 (Trigger B; persistence package
@deepseek-ai/dsh-session-persistence-jsonl0.1.2-rc.1) / Windows 11 / Node v24.14.1, 2026-09-04.Environment
packChunks: true)web,hindsight) sharing the global session store~/.dsh/sessionsagent/inbox/spliced,target: "next-step")Summary
SessionLogScanner.consumeEventLinerequires strict seq continuity (event.seq === events.length,node_modules/@deepseek-ai/dsh-session-persistence-jsonl/lib/index.js:331-343), but nothing on the write side guarantees that every append path derives its seq from the log's real committed length. We have confirmed two independent ways to append rows whose seqs collide with already-committed events. Either way the committed region is permanently unobservable — every read path (gateway session observe, resume,verify_session) fails until the log is manually repaired — and the failure surfaces as one of two misleading messages:Both messages are misleading: the zstd container is perfectly healthy (all frames complete and decodable, no physically torn tail — that case IS handled via
tornMarker.truncateTo). The actual defect is a committed-region seq regression detected by the scanner; the first message surfaces via thecheckpoint()path (committedBytes !== inputBytes, lib/index.js:1040-1041), the second via the pending-issue +turn/endthrow path (lib/index.js:331-343). Which one appears depends on whether aturn/endrow follows the collision.Trigger A — stale projection-cache hydrate baseline across process restarts
seq: 17329while the log's true committed length is 17331 events (seq 0..17330).agent/inbox/splicedwithoutcome: "canceled"). B appends a row withseq = 17329, which already exists in the log.Trigger B — interrupt / inbox-splice closers race in-flight streamer batches (single live process, no restart)
assistant/chunkreasoning batches. The streamer's next batch (seqs 34892..34905, 14 events) is generated and queued with its cursor at 34892.step/endseq 34892 andturn/end{reason:{kind:"interrupted"}}seq 34893 (ts T+993670), thensession/end-seedseq 34894 (T+993833) and — because the memory plugin had profile context queued —agent/inbox/splicedseq 34895 (T+994518, an 11,617-byte profile payload). The log's committed next-seq is now 34896.seq gap in committed region at line 4319 (expected 34894, got 34892)); the ~4,300 events / ~480 frames appended after it are unreachable until manual repair.Evidence
Fleet scan (Trigger A, 2026-09-04)
scanZstdFrames+SessionLogScanner(nativenode:zlibzstd, same as DSH):Single-session forensics (Trigger B, 2026-09-04, 0.1.2-rc.1)
step/end 34892→turn/end(interrupted) 34893→ streamer batch re-using 34892..34905 (14 chunks) →session/end-seed 34894→agent/inbox/spliced 34895→ streamer batches 34906..34925 → …sourceEventSeqsprovenance stays byte-valid). Result: 39,209 contiguous events (0..39208); the session then loads (platformverify_session: 3 turns, 39,209 events; only the normal half-open-tail findings of an interrupted session).Suggested fixes
seqis not exactly the log's committed next-seq (fail loudly, re-observe from the log, or force full replay), instead of sealing a colliding row into a new frame. This is the only fix that covers both triggers.agent/inbox/spliced, cache-hydrated writers — through one serialized seq reservation, or require closer paths to rebase on the committed length at append time and join the same queue. Trigger B shows two in-process cursors diverging; reserving34892..34905for the queued batch would have made the closers wait or renumber.corrupt session log: committed region breaks at seq N (expected N, got M) — likely stale hydrate baseline or concurrent writer. Today's messages sent us auditing zstd framing for nothing.Workaround (until fixed)
~/.dsh/storages/session_projcache/sessions/*.json, then restart (no cache record → full replay → correct baseline; first open of each session is slower).assertZstdHeaderFrame, lib/index.js:791-793). Do not renumber seqs —sourceEventSeqsprovenance is absolute. This recipe was re-validated 2026-09-04: it restored a 39,209-event session losslessly.Related upstream discussions: #3401, #4767. Reported by a DSH user; happy to provide the corrupted log samples and the repair scripts if useful.
All reactions