xai-org/grok-build · error · io::Error
session persistence actor stopped before durable append disp
Error message
session persistence actor stopped before durable append dispatch
What it means
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.
Source
Thrown at crates/codegen/xai-grok-shell/src/session/persistence.rs:1344
///
/// [`DurableAppendError::NotCommitted`] is safe to retry; [`DurableAppendError::Committed`]
/// means the replay line landed; [`DurableAppendError::AcknowledgementLost`] has unknown status.
/// No-op handles return `Unsupported`.
pub(crate) async fn append_update_durably(
&self,
update: SessionUpdate,
) -> Result<(), DurableAppendError> {
if self.noop {
return Err(DurableAppendError::NotCommitted(io::Error::new(
io::ErrorKind::Unsupported,
"durable session update append is unsupported by a no-op persistence handle",
)));
}
let (respond_to, response) = tokio::sync::oneshot::channel();
self.tx
.send(PersistenceMsg::AppendUpdateDurablyAndAck { update, respond_to })
.map_err(|_| {
DurableAppendError::NotCommitted(io::Error::new(
io::ErrorKind::BrokenPipe,
"session persistence actor stopped before durable append dispatch",
))
})?;
response
.await
.map_err(|_| {
DurableAppendError::AcknowledgementLost(io::Error::new(
io::ErrorKind::BrokenPipe,
"session persistence actor stopped before durable append acknowledgement",
))
})?
.map_err(DurableAppendError::from)
}
}
enum PendingAppendOutcome {
CommittedOk(acp::SessionNotification),View on GitHub (pinned to bc7f02eddd)
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
Defensive patterns
Strategy: retry
Validate before calling
if session.is_closed() || session.persistence_handle_is_gone() {
bail!("cannot durably append: persistence actor already stopped");
} Type guard
fn actor_alive(tx: &mpsc::Sender<PersistenceMsg>) -> bool {
!tx.is_closed()
} Try / catch
match session.append_update_durably(update).await {
Err(DurableAppendError::NotCommitted(e)) => {
tracing::warn!(%e, "append not committed; safe to retry");
session.append_update_durably(update).await?;
}
Err(e) => return Err(e.into()),
Ok(_) => {}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- session persistence actor stopped before durable append ackn
- Connection cancelled
- process scope already closed; fetch killed{}
- PTY write channel closed
- Task panicked: {}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/c59ffce1578fb712.
Report an issue: GitHub.