affaan-m/ECC · error
DB writer channel closed
Error message
DB writer channel closed
What it means
SessionStateHandle::send forwards DB mutations over an mpsc channel to a dedicated writer task; if that task has exited, the UnboundedSender fails and this error is returned to update_state/update_pid/append_output_line/touch_heartbeat callers. It means the session's persistence pipeline is dead — no further DB writes will land.
Solutions
- Recreate/reopen the DB writer (restart the session runtime) — the current handle cannot recover
- Guard callers so they stop issuing writes once the session is stopped/torn down
- Check the writer's open_error / stderr log for why run_db_writer exited (disk full, permissions, lock)
Example fix
// before
db_writer.append_output_line(line).await?; // panics/errors if writer dropped
// after
if db_writer.is_closed() {
eprintln!("db writer unavailable; skipping output line");
} else {
db_writer.append_output_line(line).await?;
} Defensive patterns
Strategy: retry
Validate before calling
if db_writer.is_closed() {
anyhow::bail!("db writer stopped; restart the session runtime before writing");
} Try / catch
if let Err(e) = db_writer.touch_heartbeat().await {
if e.to_string().contains("channel closed") {
eprintln!("persistence pipeline dead; recreating writer...");
db_writer = recreate_writer(&db_path, &session_id).await?;
} else { return Err(e); }
} Prevention
- Recreate the writer after stop_session instead of reusing handles
- Monitor run_db_writer exit reasons (open errors) early
- Ensure the session DB directory exists and is writable at startup
When it happens
Trigger: The run_db_writer task panicked or returned (e.g. fatal open error on the SQLite file) while the session handle is still used; writing DB messages after the writer was dropped at session teardown.
Common situations: Corrupt or locked session DB causing the writer to exit; calling touch_heartbeat/append_output_line after stop_session dropped the writer; DB directory deleted at runtime.
Related errors
- DB writer acknowledgement dropped
- an active harness configuration already exists
- atomic promotion compare-and-swap failed
- atomic rollback compare-and-swap failed
- baseline is not the active harness configuration
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/3d99a74b723f2066.
Report an issue: GitHub.
Appendix: source
Thrown at ecc2/src/session/runtime.rs:71
}
async fn append_output_line(&self, stream: OutputStream, line: String) -> Result<()> {
self.send(|ack| DbMessage::AppendOutputLine { stream, line, ack })
.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 } => {View on GitHub (pinned to 8321021c54)