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
- If you develop against these crates: audit your changes for any extra `inner.rebase.take()`/move of the rebase field before commit processing.
- Downgrade to the last known-good build to confirm the invariant holds there.
- For released builds, capture the panic backtrace and repository state and report it as a bug against but-transaction.
- 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
- Contributors: never add a second take()/move of inner.rebase; keep the single one at the destructuring site.
- Pin to the last known-good release when this appears after an upgrade.
- Always attach the backtrace and repro steps when reporting — it is an invariant breach, not user error.
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
- BUG: {id} is queued
- broker already configured
- broker has not been configured
- BUG: It should not be possible to omit sources
- candidate turns always have review or branch evidence
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/1b01461f28594db0.
Report an issue: GitHub.