gitbutlerapp/gitbutler · critical

rebase is always Some(_)

Error message

rebase is always Some(_)

What it means

An internal invariant panic in but-transaction: Inner::rebase is constructed as Some(rebase) and, apart from this take(), nothing clears it, so destructuring expects it to still be present at transaction commit time. Hitting this means the transaction machinery was driven in an unsupported way (the rebase was consumed/absented before finish) — i.e., a library bug or an API misuse, not a user-data problem.

Source

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

        let callback_outcome = match callback_outcome {
            Ok(outcome) => outcome,
            Err(err) => {
                inner.pending_ref_changes.rollback(&repo)?;
                return Err(err);
            }
        };

        let Inner {
            mut rebase,
            commit_mappings: _,
            pending_metadata_removals,
            pending_metadata_updates,
            pending_created_independent_refs,
            mut pending_ref_changes,
            context_lines: _,
            materialize_without_checkout,
        } = inner;
        let rebase = rebase.take().expect("rebase is always Some(_)");

        let should_rollback = callback_outcome.should_rollback();
        // A rolled-back transaction never materializes, so it has no workspace to report.
        let workspace = if should_rollback {
            Ok(None)
        } else {
            workspace_state_from_rebase(
                rebase,
                &repo,
                pending_metadata_removals,
                pending_metadata_updates,
                pending_created_independent_refs,
                dry_run,
                matches!(
                    materialize_without_checkout,
                    MaterializeWithoutCheckout::Yes
                ),
            )

View on GitHub (pinned to caf1f223d3)

Solutions

  1. If you develop against these crates: audit your changes for any extra `inner.rebase.take()`/move of the rebase field before commit processing.
  2. Downgrade to the last known-good build to confirm the invariant holds there.
  3. For released builds, capture the panic backtrace and repository state and report it as a bug against but-transaction.
  4. Retry the operation after a fresh app/process start; if it reproduces deterministically, the transaction state for that workspace is likely hitting the buggy path every time.
Defensive patterns

Strategy: try-catch

Try / catch

// process-level: this is a panic, not a Result — catch_unwind in Rust hosts,
// or in Node capture the napi error and restart the worker/session:
try {
  await api.runWorkspaceOperation(...);
} catch (e) {
  if (String(e?.message).includes('rebase is always Some')) {
    await restartAppSession(); // recover process state, then report the bug
  }
  throw e;
}

Prevention

When it happens

Trigger: A code path between Transaction construction and outcome processing that takes/None-out inner.rebase (new misuse of the Transaction API), or a regression that moved the take() earlier; triggered by any workspace operation run through the transaction wrapper.

Common situations: Contributors adding a method on Transaction that also takes `rebase`; refactors that changed the Inner lifecycle; extremely rare in released builds.

Related errors


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