gitbutlerapp/gitbutler · error
Failed to persist action: {e}
Error message
Failed to persist action: {e} What it means
anyhow wrapper at crates/but-action/src/action.rs:152 around the SQLite insert of a ButlerAction row into the project's butler_actions table via db.butler_actions_mut().insert(...). Any insert failure — database locked, constraint or IO error, disk full — surfaces as "Failed to persist action: {e}" with the underlying error chained. The row conversion (action.try_into()?) runs before the map_err and keeps its own conversion error.
Source
Thrown at crates/but-action/src/action.rs:152
Self {
id: Uuid::new_v4(),
created_at: chrono::Local::now().naive_local(),
handler,
external_prompt,
external_summary,
snapshot_before,
snapshot_after,
response: rsp.cloned(),
error,
source,
}
}
}
fn persist_action(db: &mut DbHandle, action: ButlerAction) -> anyhow::Result<()> {
db.butler_actions_mut()
.insert(action.try_into()?)
.map_err(|e| anyhow::anyhow!("Failed to persist action: {e}"))?;
Ok(())
}
/// Persist a completed handle-changes action record and return its generated ID.
///
/// `db` is the already-open project database handle to write into. `change_summary`,
/// `external_prompt`, `handler`, and `source` describe the request that produced the action.
/// `snapshot_before` and `snapshot_after` link the action to the surrounding oplog snapshots.
/// `response` is stored as either the successful action outcome or the error text.
#[expect(clippy::too_many_arguments)]
pub(crate) fn record_handle_changes_action(
db: &mut DbHandle,
change_summary: &str,
external_prompt: Option<String>,
handler: crate::ActionHandler,
source: Source,
snapshot_before: gix::ObjectId,
snapshot_after: gix::ObjectId,View on GitHub (pinned to 2497b8007a)
Solutions
- Read the chained cause {e} — "database is locked", "no such table", and "disk I/O error" each point to different fixes
- Close other GitButler processes (desktop/CLI/TUI) touching the same project and retry
- Ensure but-db migrations run on open and the schema version matches this binary
- Check write permissions on the project database file and free disk space
Example fix
// before
.insert(action.try_into()?).map_err(|e| anyhow::anyhow!("Failed to persist action: {e}"))?;
// after — keep the action id in the message so the failed record is identifiable
let row: ButlerActionRow = action.try_into()?;
let id = row.id.clone();
db.butler_actions_mut()
.insert(row)
.map_err(|e| anyhow::anyhow!("Failed to persist action {id}: {e}"))?; Defensive patterns
Strategy: retry
Try / catch
use anyhow::Context;
let mut attempts = 0u32;
loop {
match persist_action(&mut db, action.clone()) {
Ok(()) => break,
Err(e) if attempts < 5 && e.to_string().contains("database is locked") => {
std::thread::sleep(std::time::Duration::from_millis(50 << attempts));
attempts += 1; // transient SQLITE_BUSY: back off and retry
}
Err(e) => return Err(e.context("persist butler action")),
}
} Prevention
- Run only one GitButler process (desktop/CLI/TUI) per project database at a time
- Let but-db migrations run on open; never reuse a database across schema versions
- Keep the project database on local disk, not network or synced drives
- Retry transient SQLITE_BUSY errors with backoff before surfacing them to users
When it happens
Trigger: Another process holds the project database write lock (desktop app plus CLI on the same project, SQLITE_BUSY); the butler_actions table is missing after opening a database created by an older binary; the DB file or directory is read-only; disk full during the write.
Common situations: Running but CLI or TUI commands while the desktop app has the project open; database on a network or synced drive; migrations not applied after a version jump; permission changes on the project storage directory.
Related errors
- Failed to list actions: {e}
- CliInstallCancelled
- fetch timestamp does not fit in the database: {err}
- Failed to create pull request: {status} - {error_text}
- Failed to verify Bitbucket repository access
AI-assisted analysis of gitbutlerapp/gitbutler@2497b8007a (2026-08-17).
Data as JSON: /api/errors/aebda9c923db8416.
Report an issue: GitHub.