Repository navigation
[Bug] Persistence pipeline wedges permanently after one failed drain batch — session becomes a silent zombie until service restart #10307
Replies: 1 comment
|
The pause is real and the timer really is not re-armed. One correction before the write-up: there is a retry entry point, and the report is stronger if it says what is missing rather than saying nothing retries.
So after a failed batch, But So the accurate defect is: recovery depends on an external Two supporting details from the same code:
The fix I would propose matches your framing: on failure, re-arm a bounded backoff timer instead of relying on the next flush, and surface the paused state (a warning, or a |
Uh oh!
There was an error while loading. Please reload this page.
[Bug] Persistence pipeline wedges permanently after one failed drain batch — session becomes a silent zombie until service restart
Summary
A single failed
drainBuffered()batch in@deepseek-ai/dsh-session-persistence-jsonlsetsdrainPaused = trueand the drain timer is never re-armed. From that moment the session's durable log never receives another byte while the process keeps running: all subsequent events (includingstep/end,turn/endand any later turns) accumulate in the in-memory buffer only. There is no retry, no backoff, and no warning surfaced to the user. The session keeps accepting messages, so the failure is completely silent until the user notices history is not being saved.Environment
Forensic timeline of the observed incident (all times MDT)
status 499 "the agent canceled the request", in/out=0; TTFT 2.98s had already arrived). Cancel cause was one of user/parent/disposed (hook path excluded — provider is a custom magpie route, not deepseek-account).session.v4.jsonl.zstd(seq 1896 =step/start) and to the projection cache. From this point the session writes nothing to disk for the lifetime of the process (verified: zero file growth for ~60 minutes, while other sessions in the same process kept persisting normally).turn/endis appended in memory only: the live event stream reaches seq 1912 vs. seq 1896 on disk; 16 events exist solely in the buffer.Root cause (from the shipped lib)
@deepseek-ai/dsh-session-persistence-jsonl/lib/index.js:enqueueLive()(≈L187-194) pushes the event intothis.bufferedand arms a 200 ms timer that callsdrainLive().catch(reportBackgroundFailure).append()never propagates errors to the caller (fire-and-forget by design).drainBuffered()(≈L205-221): on any persist failure it doesthis.buffered = batch.concat(this.buffered); this.drainPaused = true; throw error.drainPausedis only cleared at the top of a successful drain pass — but once set,enqueueLive()returns early without re-arming the timer, so no further drain is ever attempted unless some other path callsflush().persistContiguous()(≈L223-241):ensureLease()→assertContiguous→ torn-tail repair →storage.persistBatch; any throw here triggers the wedge.Net effect: one transient failure (antivirus lock, transient FS error, lease hiccup) permanently and silently kills durability for that session, while the agent loop continues normally in memory.
reportBackgroundFailuredoes not surface anything user-visible in the default deployment.Expected behavior
drainPausedshould re-arm on the nextenqueueLive()after a cooldown; and/orSuggested fix directions
drainPausedis set, keeping the failed batch at the head ofbuffered.Happy to provide the full forensic timeline and the decoded session log if useful.
All reactions