t8y2/dbx · error

SQLite backup failed: {error}

Error message

SQLite backup failed: {error}

What it means

conn.backup(DatabaseName::Main, dest, None) returned an error while performing an online SQLite backup; the worker wraps the rusqlite error in this message. Common underlying causes are I/O problems on the destination, a locked/busy source, or corruption.

Source

Thrown at crates/dbx-sqlite-worker/src/runtime.rs:211

        }
        Err(error) => WorkerBody::err(error.to_string()),
    }
}

fn backup(conn: &Connection, dest: &str) -> WorkerBody {
    if dest.trim().is_empty() || dest.contains('\0') {
        return WorkerBody::err("Backup destination is invalid");
    }
    if let Some(parent) = Path::new(dest).parent() {
        if !parent.as_os_str().is_empty() {
            if let Err(error) = std::fs::create_dir_all(parent) {
                return WorkerBody::err(error.to_string());
            }
        }
    }
    match conn.backup(DatabaseName::Main, dest, None) {
        Ok(()) => WorkerBody::ok(),
        Err(error) => WorkerBody::err(format!("SQLite backup failed: {error}")),
    }
}

fn restore(conn: &mut Connection, src: &str) -> WorkerBody {
    if src.trim().is_empty() || src.contains('\0') {
        return WorkerBody::err("Restore source is invalid");
    }
    match conn.restore(DatabaseName::Main, src, None::<fn(_)>) {
        Ok(()) => WorkerBody::ok(),
        Err(error) => WorkerBody::err(format!("SQLite restore failed: {error}")),
    }
}

fn value_to_json(value: rusqlite::types::ValueRef<'_>, column_decl_type: Option<&str>) -> (serde_json::Value, bool) {
    match value {
        rusqlite::types::ValueRef::Null => (json!(null), false),
        rusqlite::types::ValueRef::Integer(value) => (json!(value), false),
        rusqlite::types::ValueRef::Real(value) => (json!(value), false),

View on GitHub (pinned to c0390bff16)

Solutions

  1. Read the wrapped {error} detail to identify the rusqlite/SQLite error code
  2. Ensure the destination path is writable and the disk has space
  3. Retry when the source database is not under heavy write load, or use a busy_timeout
  4. Verify the destination directory exists and permissions allow file creation

Example fix

// before
send(Backup { dest: "/mnt/net/b.db" }); // IOERR
// after
let dest = "/var/backups/local/b.db"; // local writable dir
send(Backup { dest }); // retry on busy with backoff
Defensive patterns

Strategy: retry

Validate before calling

fn preflight_backup(conn: &rusqlite::Connection, dest: &str) -> Result<(), String> {
    let parent = std::path::Path::new(dest).parent().ok_or("no parent dir")?;
    std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
    let f = std::fs::OpenOptions::new().write(true).create(true).open(dest).map_err(|e| e.to_string())?;
    drop(f);
    Ok(())
}

Type guard

fn backup_writable(dest: &str) -> bool {
    std::path::Path::new(dest).parent().map_or(false, |p| std::fs::metadata(p).map_or(false, |m| !m.permissions().readonly()))
}

Try / catch

match worker.send(WorkerOp::Backup { dest }).await {
    Err(body) if body.error().map_or(false, |e| e.starts_with("SQLite backup failed:")) => {
        eprintln!("Backup failed: check disk space, permissions, and source locks; retrying");
        std::thread::sleep(Duration::from_secs(2));
        worker.send(WorkerOp::Backup { dest }).await?
    }
    other => other?,
}

Prevention

When it happens

Trigger: Destination file not writable or on a full filesystem; source database locked by another writer during the backup step; disk I/O error or SQLITE_BUSY/SQLITE_IOERR surfaced through the backup API.

Common situations: Backing up while the app holds long write transactions; backing up to a network mount that drops; permissions changed on the backup directory; disk quota exceeded.

Related errors


AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05). Data as JSON: /api/errors/5a93d452ef4a4ca6. Report an issue: GitHub.