gitbutlerapp/gitbutler · error

Failed to list actions: {e}

Error message

Failed to list actions: {e}

What it means

anyhow wrapper at crates/but-action/src/action.rs:195 around the paginated SELECT over butler_actions (offset/limit). Rows that fail to convert to ButlerAction are deliberately skipped afterwards (filter_map preserves best-effort listing), so this error means the query itself failed — a locked or unreadable database or a missing table — not malformed rows.

Source

Thrown at crates/but-action/src/action.rs:195

        snapshot_before,
        snapshot_after,
        response,
        source,
    );
    let id = action.id;
    persist_action(db, action)?;
    Ok(id)
}

/// List persisted Butler actions from `db` using `offset` and `limit` pagination.
///
/// Invalid database rows are skipped to preserve the historical best-effort behavior of the
/// action list endpoint.
pub fn list_actions(db: &DbHandle, offset: i64, limit: i64) -> anyhow::Result<ActionListing> {
    let (total, actions) = db
        .butler_actions()
        .list(offset, limit)
        .map_err(|e| anyhow::anyhow!("Failed to list actions: {e}"))?;

    // Filter out any entries that cannot be converted to ButlerAction
    let actions = actions
        .into_iter()
        .filter_map(|a| TryInto::try_into(a).ok())
        .collect::<Vec<_>>();
    Ok(ActionListing { total, actions })
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ActionListing {
    pub total: i64,
    pub actions: Vec<ButlerAction>,
}

View on GitHub (pinned to 2497b8007a)

Solutions

  1. Read {e}: "no such table" means migrations must run; "database is locked" means retry after the concurrent writer finishes
  2. Upgrade to a binary whose schema matches the project database
  3. Close concurrent GitButler processes holding the project
  4. If the database is corrupt, rebuild the project database — action history is best-effort data

Example fix

// before
let (total, actions) = db.butler_actions().list(offset, limit)
	.map_err(|e| anyhow::anyhow!("Failed to list actions: {e}"))?;

// after — include the pagination window for faster diagnosis
let (total, actions) = db.butler_actions().list(offset, limit)
	.map_err(|e| anyhow::anyhow!("Failed to list actions (offset {offset}, limit {limit}): {e}"))?;
Defensive patterns

Strategy: try-catch

Try / catch

match list_actions(&db, offset, limit) {
    Ok(listing) => render(listing),
    Err(e) => {
        log::warn!("action history unavailable: {e:#}");
        // do NOT fake an empty history — surface an explicit unavailable state
        render_unavailable("Action history is unavailable.");
    }
}

Prevention

When it happens

Trigger: Reading the action history when the butler_actions table does not exist (database created by an older binary without migrations), the database is locked by a concurrent writer, or the file is corrupt or unreadable.

Common situations: Opening a legacy project database without running migrations; the desktop app writing while the CLI lists actions; a truncated database file on a failing disk.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@2497b8007a (2026-08-17). Data as JSON: /api/errors/dfd69085de60cad6. Report an issue: GitHub.