Zackriya-Solutions/meetily · error · anyhow::Error
Failed to start transaction: {}
Error message
Failed to start transaction: {} What it means
A pooled connection was acquired, but issuing BEGIN failed. On SQLite this is nearly always SQLITE_BUSY at BEGIN time - another connection already holds the single write lock - or the connection was left in a bad state by an earlier error on it.
Source
Thrown at frontend/src-tauri/src/audio/retranscription.rs:434
return Err(anyhow!("Retranscription cancelled"));
}
emit_progress(&app, &meeting_id, "saving", 80, "Saving transcripts...");
// Create transcript segments with proper timestamps from VAD
let segments = create_transcript_segments(&all_transcripts);
// Save to database
let app_state = app
.try_state::<AppState>()
.ok_or_else(|| anyhow!("App state not available"))?;
// Wrap delete+insert+update in a transaction to prevent data loss
let pool = app_state.db_manager.pool();
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))?;
sqlx::query("DELETE FROM transcripts WHERE meeting_id = ?")
.bind(&meeting_id)
.execute(&mut *tx)
.await
.map_err(|e| anyhow!("Failed to delete existing transcripts: {}", e))?;
for segment in &segments {
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)View on GitHub (pinned to 0281737d87)
Solutions
- Read the inner error: 'database is locked' means retry after the concurrent writer finishes.
- Serialize database writers through a single writer queue or task so only one transaction is open at a time.
- Enable busy_timeout/WAL on the pool so BEGIN waits for the lock instead of erroring.
- Retry the retranscription once the other operation completes.
Defensive patterns
Strategy: retry
Try / catch
// Retry the whole short transaction once on BEGIN failure
let tx = match sqlx::Connection::begin(&mut *conn).await {
Ok(t) => t,
Err(e) => {
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
sqlx::Connection::begin(&mut *conn).await?
}
}; Prevention
- Serialize all database writers (single writer task or queue) to avoid concurrent BEGIN.
- Enable busy_timeout on the SQLite connection string/options.
- Keep write transactions short: prepare all rows first, then write in one burst.
When it happens
Trigger: Another task opens a write transaction (import saving, live transcript writes, summary update) at the exact moment retranscription starts its delete+insert transaction; nested or abandoned transactions on the same connection.
Common situations: Simultaneous DB-heavy operations finishing at the same time; a background sync task that writes during retranscription save.
Related errors
- Failed to start transaction: {}
- Failed to commit transaction: {}
- DB error: {}
- Failed to commit transaction: {}
- DB error: {}
AI-assisted analysis of Zackriya-Solutions/meetily@0281737d87 (2026-08-16).
Data as JSON: /api/errors/5e8d58ff260a954e.
Report an issue: GitHub.