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

  1. Check whether the update was actually committed (read the updates file) and re-append if not; treat NotCommitted as retryable
  2. Fix actor lifetime: keep the actor task alive until all in-flight appends complete (graceful drain before shutdown)
  3. Hold the session handle open while appending; don't append after close()
  4. 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

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


AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31). Data as JSON: /api/errors/c59ffce1578fb712. Report an issue: GitHub.