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
- If you implement `TransactionOutcome`, make `should_rollback` return true for every variant that represents cancellation, abort, or an empty result
- Don't call `into_outcome`/`committed_workspace` yourself; let `with_transaction` drive the lifecycle
- If no custom outcome type is involved, capture the transaction inputs and report it as a but-transaction bug
- 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
- Let with_transaction drive into_outcome; never call it manually
- Unit-test should_rollback for every variant of your outcome type
- Keep but-transaction and but-workspace versions in lockstep
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
- AtSegment anchor always has oldest commit resolved
- known statically
- if it was found before it will be found as id
- anchor is always present in the order at this point
- checked all selectors are present
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/54cf82474c6b04bb.
Report an issue: GitHub.