gitbutlerapp/gitbutler · error

a committed transaction always materializes a workspace

Error message

a committed transaction always materializes a workspace

What it means

Panic from `committed_workspace`'s `expect("a committed transaction always materializes a workspace")` (but-transaction, lib.rs:1247). The `TransactionOutcome` trait contract says `into_outcome` receives `Some(workspace)` exactly when `should_rollback()` returned false. The panic fires when a value that claims not to roll back reaches the end of a commit path that produced no workspace — i.e. the contract between the outcome type and the commit machinery broke.

Source

Thrown at crates/but-transaction/src/lib.rs:1247

    impl<T> Sealed for super::Commit<T> {}
}

pub trait TransactionOutcome: sealed::Sealed {
    type Outcome;

    fn should_rollback(&self) -> bool;

    /// Package the callback's value together with the workspace the transaction produced.
    ///
    /// `workspace` is `Some` exactly when [`Self::should_rollback`] returned `false`; a
    /// rolled-back transaction is never materialized and so has no workspace to report.
    fn into_outcome(self, workspace: Option<WorkspaceState>) -> Self::Outcome;
}

/// The workspace state that [`TransactionOutcome::into_outcome`] is handed whenever the
/// transaction commits.
fn committed_workspace(workspace: Option<WorkspaceState>) -> WorkspaceState {
    workspace.expect("a committed transaction always materializes a workspace")
}

impl TransactionOutcome for () {
    type Outcome = WorkspaceState;

    fn should_rollback(&self) -> bool {
        false
    }

    fn into_outcome(self, workspace: Option<WorkspaceState>) -> Self::Outcome {
        committed_workspace(workspace)
    }
}

/// Statically roll back the current transaction.
#[must_use = "`Rollback` must be returned from `with_transaction` for the transaction to be rolled back"]
pub struct Rollback<T>(T);

View on GitHub (pinned to caf1f223d3)

Solutions

  1. If you implement `TransactionOutcome`, make `should_rollback` return true for every variant that represents cancellation, abort, or an empty result
  2. Don't call `into_outcome`/`committed_workspace` yourself; let `with_transaction` drive the lifecycle
  3. If no custom outcome type is involved, capture the transaction inputs and report it as a but-transaction bug
  4. Pin matching versions of but-transaction and but-workspace so commit paths and outcome handling agree

Example fix

// before
impl TransactionOutcome for MyOutcome {
    fn should_rollback(&self) -> bool {
        false // too broad: aborted runs also claim to commit
    }
    ...
}

// after
impl TransactionOutcome for MyOutcome {
    fn should_rollback(&self) -> bool {
        matches!(self, MyOutcome::Aborted | MyOutcome::Cancelled)
    }
    ...
}
Defensive patterns

Strategy: validation

Validate before calling

// If you implement TransactionOutcome, verify every variant's rollback answer:
impl TransactionOutcome for MyOutcome {
    fn should_rollback(&self) -> bool {
        matches!(self, MyOutcome::Aborted | MyOutcome::Cancelled /* every non-commit shape */)
    }
    fn into_outcome(self, ws: Option<WorkspaceState>) -> WorkspaceState {
        debug_assert!(ws.is_some(), "committed path must materialize a workspace");
        committed_workspace(ws)
    }
}

Prevention

When it happens

Trigger: Implementing `TransactionOutcome` for a custom type whose `should_rollback` returns false for a variant that actually represents an aborted/empty transaction; a library change where a commit path can now finish without materializing a workspace while outcomes still claim success; calling `into_outcome` manually with None.

Common situations: Downstream crates adding their own outcome wrappers around `with_transaction`; version skew between but-transaction and a fork that added new commit paths; unit tests that construct outcome values directly instead of going through the transaction.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/54cf82474c6b04bb. Report an issue: GitHub.