affaan-m/ECC · error

error

Error message

error

What it means

After the DB message is queued, send awaits a oneshot ack from the writer. Ok(Err(error)) means the writer received the message but the underlying DB operation failed; the inner error string is re-wrapped verbatim as anyhow "error"-style message. This is a propagated persistence failure, not a channel failure.

Solutions

  1. Inspect the inner error text (it is embedded in this error) to find the actual DB failure
  2. Retry the write after the lock holder releases the database
  3. Back up/repair or recreate the session DB if the schema is corrupted

Example fix

// before
Err(anyhow::anyhow!(error))
// after
Err(anyhow::anyhow!("DB write failed: {error}")) // preserve cause in message
Defensive patterns

Strategy: retry

Validate before calling

// check DB writability before writes
let f = std::fs::OpenOptions::new().append(true).open(&db_path)?;

Try / catch

match db_writer.update_state(state).await {
    Err(e) if is_db_error(&e) => {
        tokio::time::sleep(Duration::from_millis(250)).await;
        db_writer.update_state(state).await?; // retry once after transient lock
    }
    r => r?,
}

Prevention

When it happens

Trigger: run_db_writer executes the write against SQLite and returns Err — disk full, schema mismatch, locked database, constraint violation — and the ack carries that error back to update_state/update_pid/append_output_line/touch_heartbeat.

Common situations: SQLite file locked by another process (concurrent CLI inspecting the session); disk quota exceeded; DB schema version drift after an upgrade.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/c5eb3beaa59d0163. Report an issue: GitHub.

Appendix: source

Thrown at ecc2/src/session/runtime.rs:75

            .await
    }

    async fn touch_heartbeat(&self) -> Result<()> {
        self.send(|ack| DbMessage::TouchHeartbeat { ack }).await
    }

    async fn send<F>(&self, build: F) -> Result<()>
    where
        F: FnOnce(oneshot::Sender<DbAck>) -> DbMessage,
    {
        let (ack_tx, ack_rx) = oneshot::channel();
        self.tx
            .send(build(ack_tx))
            .map_err(|_| anyhow::anyhow!("DB writer channel closed"))?;

        match ack_rx.await {
            Ok(Ok(())) => Ok(()),
            Ok(Err(error)) => Err(anyhow::anyhow!(error)),
            Err(_) => Err(anyhow::anyhow!("DB writer acknowledgement dropped")),
        }
    }
}

fn run_db_writer(db_path: PathBuf, session_id: String, mut rx: mpsc::UnboundedReceiver<DbMessage>) {
    let (opened, open_error) = match StateStore::open(&db_path) {
        Ok(db) => (Some(db), None),
        Err(error) => (None, Some(error.to_string())),
    };

    while let Some(message) = rx.blocking_recv() {
        match message {
            DbMessage::UpdateState { state, ack } => {
                let result = match opened.as_ref() {
                    Some(db) => db
                        .update_state(&session_id, &state)
                        .map_err(|error| error.to_string()),

View on GitHub (pinned to 8321021c54)