Zackriya-Solutions/meetily · error

Failed to commit transaction: {}

Error message

Failed to commit transaction: {}

What it means

`tx.commit()` failed while flushing the meeting row plus every transcript insert. SQLite returns errors at commit for SQLITE_BUSY (write lock lost to another connection during the long insert loop), SQLITE_FULL or disk I/O errors, or a database file that vanished mid-import. The rollback discards a fully completed transcription — the costliest failure point of the pipeline.

Source

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

        sqlx::query(
            "INSERT INTO transcripts (id, meeting_id, transcript, timestamp, audio_start_time, audio_end_time, duration)
             VALUES (?, ?, ?, ?, ?, ?, ?)",
        )
        .bind(&segment.id)
        .bind(&meeting_id)
        .bind(&segment.text)
        .bind(&segment.timestamp)
        .bind(segment.audio_start_time)
        .bind(segment.audio_end_time)
        .bind(segment.duration)
        .execute(&mut *tx)
        .await
        .map_err(|e| anyhow!("Failed to insert transcript: {}", e))?;
    }

    tx.commit()
        .await
        .map_err(|e| anyhow!("Failed to commit transaction: {}", e))?;

    info!(
        "Created meeting '{}' with {} transcripts",
        meeting_id,
        segments.len()
    );

    Ok(meeting_id)
}

/// Get or initialize the Whisper engine
async fn get_or_init_whisper<R: Runtime>(
    app: &AppHandle<R>,
    requested_model: Option<&str>,
) -> Result<Arc<WhisperEngine>> {
    use crate::whisper_engine::commands::WHISPER_ENGINE;

    let engine = {

View on GitHub (pinned to 0281737d87)

Solutions

  1. Enable WAL plus busy_timeout so COMMIT waits for the lock instead of failing
  2. Keep the transaction short — commit in chunks (e.g. per N transcripts) so the write-lock window is small
  3. Check disk space and ensure the DB directory is not cloud-synced or externally locked
  4. On SQLITE_BUSY, retry the whole create_meeting_with_transcripts operation once — a failed commit cannot be retried itself since the transaction is consumed

Example fix

// before — one long-held write lock across all transcripts
tx.commit().await.map_err(|e| anyhow!("Failed to commit transaction: {}", e))?;

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

Strategy: retry

Validate before calling

// pre-commit sanity: writable DB dir and enough disk for the write
let meta = tokio::fs::metadata(&db_path).await?;
// plus: PRAGMA journal_mode should be 'wal' to minimize lock windows

Try / catch

A failed commit consumes the transaction — retry by re-running the whole create_meeting_with_transcripts (idempotent for a fresh UUID meeting id) once after a short backoff when the error contains 'database is locked'; never retry on disk-full or I/O errors.

Prevention

When it happens

Trigger: A concurrent writer takes the SQLite write lock between BEGIN and COMMIT during the (potentially thousands-row) transcript insert loop; the disk fills during the import; the DB file is moved, deleted, or locked by an external process at commit time.

Common situations: Hour-long meeting imports (huge transaction window) racing live recording saves; low-disk machines; cloud-sync clients locking the DB file exactly at commit.

Related errors


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