{"record":{"id":"a866ffd0ea0d5f80","repo":"xai-org/grok-build","slug":"session-persistence-actor-stopped-before-durable-a-a866ff","errorCode":null,"errorMessage":"session persistence actor stopped before durable append acknowledgement","messagePattern":"session persistence actor stopped before durable append acknowledgement","errorType":"error_code","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/codegen/xai-grok-shell/src/session/persistence.rs","lineNumber":1352,"sourceCode":"        if self.noop {\n            return Err(DurableAppendError::NotCommitted(io::Error::new(\n                io::ErrorKind::Unsupported,\n                \"durable session update append is unsupported by a no-op persistence handle\",\n            )));\n        }\n        let (respond_to, response) = tokio::sync::oneshot::channel();\n        self.tx\n            .send(PersistenceMsg::AppendUpdateDurablyAndAck { update, respond_to })\n            .map_err(|_| {\n                DurableAppendError::NotCommitted(io::Error::new(\n                    io::ErrorKind::BrokenPipe,\n                    \"session persistence actor stopped before durable append dispatch\",\n                ))\n            })?;\n        response\n            .await\n            .map_err(|_| {\n                DurableAppendError::AcknowledgementLost(io::Error::new(\n                    io::ErrorKind::BrokenPipe,\n                    \"session persistence actor stopped before durable append acknowledgement\",\n                ))\n            })?\n            .map_err(DurableAppendError::from)\n    }\n}\n\nenum PendingAppendOutcome {\n    CommittedOk(acp::SessionNotification),\n    CommittedErr(acp::SessionNotification, io::Error),\n    NotCommittedErr(acp::SessionNotification, io::Error),\n}\n\nstruct SessionPersistence {\n    info: Info,\n    storage: Arc<dyn StorageAdapter>,\n    /// Pending ACP notification for merging consecutive text chunks","sourceCodeStart":1334,"sourceCodeEnd":1370,"githubUrl":"https://github.com/xai-org/grok-build/blob/bc7f02eddd3d84085849dc19ed216f11c23b0571/crates/codegen/xai-grok-shell/src/session/persistence.rs#L1334-L1370","documentation":"The AppendUpdateDurablyAndAck message was successfully sent to the persistence actor, but the oneshot response channel returned RecvError because the actor dropped the responder — i.e. the actor stopped after receiving the message but before acknowledging the durable write. Mapped to DurableAppendError::AcknowledgementLost: commit state is unknown (the write may or may not have landed).","triggerScenarios":"The response.await oneshot errors because the actor task terminated between receiving the AppendUpdateDurablyAndAck message and sending the ack — actor panic mid-fsync, task abort, or runtime shutdown during the durable write.","commonSituations":"Ctrl-C/shutdown racing an fsync; actor panic while persisting; abrupt process kill during append; overly aggressive session teardown that aborts the actor task before ack.","solutions":["Verify on disk whether the update was appended before retrying (ack-lost is ambiguous; blind retry can duplicate the entry)","Make the actor drain in-flight appends and ack them before shutting down","Wrap the actor loop so panics are caught/logged instead of silently killing the task","Keep the tokio runtime alive until pending durable appends resolve"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// no pre-call check can fully prevent this; ensure the actor task is healthy\nif actor_join_handle.is_finished() {\n    bail!(\"persistence actor already exited; ack would be lost\");\n}","typeGuard":"fn ack_channel_open(rx: &oneshot::Receiver<Result<(), DurableAppendError>>) -> bool {\n    !rx.is_closed()\n}","tryCatchPattern":"match session.append_update_durably(update).await {\n    Err(DurableAppendError::AcknowledgementLost(e)) => {\n        // commit state unknown: verify on disk before retrying to avoid duplicates\n        if !updates_file_contains(&updates_path, &update) {\n            session.append_update_durably(update).await?;\n        }\n    }\n    Err(e) => return Err(e.into()),\n    Ok(()) => {}\n}","preventionTips":["Gracefully drain and ack pending appends before shutting down the actor","Catch panics in the actor loop so it doesn't drop responders mid-flight","Avoid Ctrl-C/abort during durable writes; use a graceful shutdown path","After ack loss, always check the updates file before re-appending to prevent duplicates"],"tags":["tokio","oneshot","actor","ack","durable-write","shutdown"],"backgroundTag":"actor-ack-lost","analyzedSha":"bc7f02eddd3d84085849dc19ed216f11c23b0571","analyzedAt":"2026-08-31T04:59:42.031Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}