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
- Enable WAL plus busy_timeout so COMMIT waits for the lock instead of failing
- Keep the transaction short — commit in chunks (e.g. per N transcripts) so the write-lock window is small
- Check disk space and ensure the DB directory is not cloud-synced or externally locked
- 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
- Enable WAL + busy_timeout so COMMIT waits instead of failing
- Keep transactions short — chunk transcript inserts so the write lock is held briefly
- Check disk space before long imports; keep the DB directory free of cloud-sync locks
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
- Failed to start transaction: {}
- Failed to commit transaction: {}
- DB error: {}
- Failed to create meeting: {}
- Failed to insert transcript: {}
AI-assisted analysis of Zackriya-Solutions/meetily@0281737d87 (2026-08-16).
Data as JSON: /api/errors/dabde6f65589c999.
Report an issue: GitHub.