Zackriya-Solutions/meetily · error

Failed to start transaction: {}

Error message

Failed to start transaction: {}

What it means

`sqlx::Connection::begin` failed to start the write transaction — SQLite refused BEGIN or could not take the write lock. The classic cause is SQLITE_BUSY/SQLITE_LOCKED: another connection (live recording writer, summary job, second app instance) holds the database write lock longer than busy_timeout allows. Note acquire() on the line above already succeeded, so the connection itself is healthy.

Source

Thrown at frontend/src-tauri/src/audio/import.rs:703

    );
}


/// Create a new meeting with transcripts in the database
async fn create_meeting_with_transcripts(
    pool: &sqlx::SqlitePool,
    title: &str,
    segments: &[TranscriptSegment],
    folder_path: String,
) -> Result<String> {
    let meeting_id = format!("meeting-{}", Uuid::new_v4());
    let now = chrono::Utc::now();

    // Start transaction
    let mut conn = pool.acquire().await.map_err(|e| anyhow!("DB error: {}", e))?;
    let mut tx = sqlx::Connection::begin(&mut *conn)
        .await
        .map_err(|e| anyhow!("Failed to start transaction: {}", e))?;

    // Insert meeting
    sqlx::query(
        "INSERT INTO meetings (id, title, created_at, updated_at, folder_path)
         VALUES (?, ?, ?, ?, ?)",
    )
    .bind(&meeting_id)
    .bind(title)
    .bind(now)
    .bind(now)
    .bind(&folder_path)
    .execute(&mut *tx)
    .await
    .map_err(|e| anyhow!("Failed to create meeting: {}", e))?;

    // Insert transcripts
    for segment in segments {
        sqlx::query(

View on GitHub (pinned to 0281737d87)

Solutions

  1. Enable WAL and busy_timeout on connect (PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000;) so BEGIN waits instead of failing
  2. Retry BEGIN on SQLITE_BUSY with short backoff
  3. Avoid running imports concurrently with other heavy DB write paths — the ImportGuard blocks concurrent imports, not other writers
  4. Move the DB off cloud-synced or network folders

Example fix

// before
let mut tx = sqlx::Connection::begin(&mut *conn).await
    .map_err(|e| anyhow!("Failed to start transaction: {}", e))?;

// after — configure WAL + busy_timeout once, at pool creation
SqlitePoolOptions::new()
    .max_connections(5)
    .after_connect(|conn, _| Box::pin(async move {
        sqlx::query("PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000;")
            .execute(&mut *conn)
            .await?;
        Ok(())
    }))
Defensive patterns

Strategy: retry

Validate before calling

-- verify journal mode once, at startup
PRAGMA journal_mode; -- should report 'wal'

Try / catch

Retry Connection::begin twice with 250-500ms backoff when the sqlx error string contains 'database is locked'; give up after that and report contention rather than retrying forever.

Prevention

When it happens

Trigger: Calling create_meeting_with_transcripts while another pooled connection is mid-write; the database in rollback-journal mode with concurrent readers blocking the writer; DB file on a locked or cloud-synced location.

Common situations: Importing while a live meeting recording is being saved; two app instances pointing at the same DB; the DB directory under OneDrive/Dropbox sync holding advisory locks.

Related errors


AI-assisted analysis of Zackriya-Solutions/meetily@0281737d87 (2026-08-16). Data as JSON: /api/errors/1c8572082f307dce. Report an issue: GitHub.