{"record":{"id":"c59ffce1578fb712","repo":"xai-org/grok-build","slug":"session-persistence-actor-stopped-before-durable-a","errorCode":null,"errorMessage":"session persistence actor stopped before durable append dispatch","messagePattern":"session persistence actor stopped before durable append dispatch","errorType":"error_code","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/codegen/xai-grok-shell/src/session/persistence.rs","lineNumber":1344,"sourceCode":"    ///\n    /// [`DurableAppendError::NotCommitted`] is safe to retry; [`DurableAppendError::Committed`]\n    /// means the replay line landed; [`DurableAppendError::AcknowledgementLost`] has unknown status.\n    /// No-op handles return `Unsupported`.\n    pub(crate) async fn append_update_durably(\n        &self,\n        update: SessionUpdate,\n    ) -> Result<(), DurableAppendError> {\n        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),","sourceCodeStart":1326,"sourceCodeEnd":1362,"githubUrl":"https://github.com/xai-org/grok-build/blob/bc7f02eddd3d84085849dc19ed216f11c23b0571/crates/codegen/xai-grok-shell/src/session/persistence.rs#L1326-L1362","documentation":"A durable, acknowledged append was requested by sending PersistenceMsg::AppendUpdateDurablyAndAck over the actor's mpsc channel, but the sender failed because the persistence actor's receiving loop has shut down (all receivers dropped). The library maps this to DurableAppendError::NotCommitted with io::ErrorKind::BrokenPipe: the update was NOT durably written.","triggerScenarios":"Calling the durable append API (AppendUpdateDurablyAndAck path) after the session persistence actor task has exited — e.g. actor panicked, its JoinHandle was dropped/aborted, or the session was torn down concurrently while an update was still being appended.","commonSituations":"Appends racing session close/abort at shutdown; actor task killed by panic or tokio runtime shutdown; holding a stale Session handle after close; bugs where actor exits on an earlier poison-pill message.","solutions":["Check whether the update was actually committed (read the updates file) and re-append if not; treat NotCommitted as retryable","Fix actor lifetime: keep the actor task alive until all in-flight appends complete (graceful drain before shutdown)","Hold the session handle open while appending; don't append after close()","Inspect actor logs for a panic that terminated the loop"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"if session.is_closed() || session.persistence_handle_is_gone() {\n    bail!(\"cannot durably append: persistence actor already stopped\");\n}","typeGuard":"fn actor_alive(tx: &mpsc::Sender<PersistenceMsg>) -> bool {\n    !tx.is_closed()\n}","tryCatchPattern":"match session.append_update_durably(update).await {\n    Err(DurableAppendError::NotCommitted(e)) => {\n        tracing::warn!(%e, \"append not committed; safe to retry\");\n        session.append_update_durably(update).await?;\n    }\n    Err(e) => return Err(e.into()),\n    Ok(_) => {}\n}","preventionTips":["Keep the persistence actor task alive until all in-flight appends finish (drain before shutdown)","Don't call durable appends after session.close()","Hold session handles for their full lifetime; drop order matters with tokio tasks","Check actor task JoinHandle results to catch early actor exits/panics"],"tags":["tokio","mpsc","actor","shutdown","durable-write"],"backgroundTag":"actor-channel-closed","analyzedSha":"bc7f02eddd3d84085849dc19ed216f11c23b0571","analyzedAt":"2026-08-31T04:59:42.031Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}