zeroclaw-labs/zeroclaw · error
SQLite connection open timed out after {} seconds
Error message
SQLite connection open timed out after {} seconds What it means
open_connection() optionally runs Connection::open on a helper thread and waits with recv_timeout, clamped to SQLITE_OPEN_TIMEOUT_CAP_SECS. If the timer expires (open blocking on a locked database file or a stalled filesystem), the thread is abandoned and this error reports the capped seconds — which may be lower than the configured value.
Source
Thrown at crates/zeroclaw-memory/src/sqlite.rs:153
/// Open SQLite connection, optionally with a timeout (for locked/slow storage).
fn open_connection(
db_path: &Path,
open_timeout_secs: Option<u64>,
) -> anyhow::Result<Connection> {
let path_buf = db_path.to_path_buf();
let conn = if let Some(secs) = open_timeout_secs {
let capped = secs.min(SQLITE_OPEN_TIMEOUT_CAP_SECS);
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let result = Connection::open(&path_buf);
let _ = tx.send(result);
});
match rx.recv_timeout(Duration::from_secs(capped)) {
Ok(Ok(c)) => c,
Ok(Err(e)) => return Err(e).context("SQLite failed to open database"),
Err(mpsc::RecvTimeoutError::Timeout) => {
anyhow::bail!("SQLite connection open timed out after {} seconds", capped);
}
Err(mpsc::RecvTimeoutError::Disconnected) => {
anyhow::bail!("SQLite open thread exited unexpectedly");
}
}
} else {
Connection::open(&path_buf).context("SQLite failed to open database")?
};
Ok(conn)
}
/// Initialize all tables: memories, FTS5, `embedding_cache`
fn init_schema(conn: &Connection) -> anyhow::Result<()> {
fn is_db_locked_error(e: &rusqlite::Error) -> bool {
use rusqlite::ffi::ErrorCode;
matches!(
e,View on GitHub (pinned to 88bb9c8533)
Solutions
- Find and stop the other holder: lsof <workspace>/memory/brain.db (check the -wal/-shm files too)
- Raise the sqlite open timeout setting — but note it is clamped to SQLITE_OPEN_TIMEOUT_CAP_SECS, so values beyond the cap won't help
- Move the workspace (or at least brain.db) to fast local storage, not NFS/SMB
- If a dead process left stale locks, verify nothing is running, remove brain.db-wal and brain.db-shm, and retry
Defensive patterns
Strategy: retry
Validate before calling
// Before startup, detect another live holder of the database
// (shell): lsof <workspace>/memory/brain.db ; return non-zero output -> refuse to start
let locked = std::process::Command::new("lsof").arg(db_path).output()?.status.success(); Type guard
fn is_sqlite_open_timeout(e: &anyhow::Error) -> bool {
e.to_string().starts_with("SQLite connection open timed out")
} Try / catch
for attempt in 0..3 {
match SqliteMemory::new(ws, timeout).await {
Ok(m) => break Ok(m),
Err(e) if is_sqlite_open_timeout(&e) && attempt < 2 => { free_locks_or_wait(); }
Err(e) => break Err(e),
}
} Prevention
- Run exactly one gateway instance per workspace
- Keep brain.db on local disk, never NFS/SMB
- Set the sqlite open timeout with the internal cap in mind
- Exclude brain.db* from antivirus/indexer scans
When it happens
Trigger: Another process holds workspace/memory/brain.db (a second gateway instance or stray daemon); the database lives on NFS/SMB where POSIX locks hang; very slow or failing storage; the configured timeout exceeds the internal cap.
Common situations: Two zeroclaw instances on one workspace; Docker bind-mounts on macOS/Windows with slow I/O; antivirus or indexers holding brain.db; NFS home directories.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- purge_agent not supported by this memory backend
- rename_agent not supported by this memory backend
- ACP elicitation/create timed out after {timeout:?}
- ACP elicitation/create (multi) timed out after {timeout:?}
- ACP request_permission timed out after {:?}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/cc9254b1aa064ce1.
Report an issue: GitHub.