Replies: 4 comments
|
Thanks for decompressing and pinning the exact line - that is the smoking gun. Reading
Likely cause to verifyTwo writers both incremented from the same observed Recovery todayThe content is intact - export it with dsh-shelf (works even when the session won't load): npx dsh-shelf rescue <id>Happy to prepare a cherry-pick-ready patch once I can reproduce the exact interleaving (a captured session dir + the events around line 30976 would let me pin the two writers). |
|
Shipped the defensive half while the exact interleaving still can't be reproduced deterministically. Why it isn't a mid-append race
Shipped: tolerate one duplicate on loadBranch:
This is the "don't lose the whole history over one duplicated row" fix. The durable fix — serializing the restore/adopt boundary so two logical writers can't coexist — still needs your captured |
|
sqlite parity is done too: The durable fix (serializing the reopen/adopt boundary so the two writers can't coexist) still needs the repro sample. Both backends now survive the symptom regardless. |
|
Thanks for the detailed write-up — the pinned dsh-doctor i.e. it distinguishes the three shapes you describe — gaps ( Two notes for the thread:
Happy to open a PR or a discussion over in |
Uh oh!
There was an error while loading. Please reload this page.
Bug: two different session events share the same seq — "corrupt session log: seq gap" on load
Environment
@deepseek-ai/dsh0.1.0-rc.6 (web profile, Windows 11)session.jsonl.zstd(zstd, packed chunk rows)Symptom
Loading a session fails with:
Evidence (from the damaged artifact)
Decompressing the log and inspecting around the reported line shows two different events that were both allocated seq 461420:
{"type":"session/end-seed","seq":461420,...} {"type":"agent/inbox/spliced","seq":461420,"time":1786716691871, "data":{"target":"next-turn","start":0,"inserted":[{...user message...}]}} {"type":"turn/start","seq":461421,...}The loader (
SessionLogScanner.consumeEventLine) requiresevent.seq === events.length(0-based expanded-event index). Becauseagent/inbox/splicedcarries the same seq as the precedingsession/end-seed, the check fails and the whole log is refused.All 471,075 expanded events before and after this point are strictly contiguous (verified by replaying the same
decodeStorageRecordexpansion the loader uses), so this is the single corruption in the file.Root-cause hypothesis
The two events come from different write paths:
session/end-seed— appended by theSessionconstructor (seed boundary on reopen/resume),agent/inbox/spliced— appended by the agent loop when a user message arrives.Both received seq 461420, and the counter continued correctly at 461421 afterwards. This looks like a race in seq allocation across concurrent appends (e.g., the next-seq value was derived from a stale log-length snapshot, or the append path is not serialized across these two writers). Since the two lines carry different event types, this is not a torn-write duplicate of one event — the allocator handed out the same sequence number twice.
Impact
A session with one duplicated seq becomes entirely unloadable (the loader refuses the whole log, not just the bad event). The user loses access to the whole conversation history until manual repair.
Suggested direction
session/end-seedvsagent/inbox/splicedinterleaving around session reopen while a message is queued.Notes
agent/inbox/splicedrow and rebuilding the zstd frames (frame 1 = header line, frame 2 = events); the session loads fine afterwards. The paired inbox-removal row and theuser/messagefor the same input were both intact, so no model-visible history was lost.All reactions