gitbutlerapp/gitbutler · error
Fixup commit must not come after a reference step
Error message
Fixup commit must not come after a reference step
What it means
Rebase plans are validated step by step. A SquashIntoPreceding (fixup) step squashes its commit into the immediately preceding step, so the preceding step must be a commit-bearing pick. If the last recorded step is a Reference step, there is no preceding pick to squash into, and validation rejects the plan.
Solutions
- Reorder so the fixup follows the pick it should squash into, not a reference.
- Insert the reference after the fixup step instead of before it.
- Drop the reference step if it is not needed at that position.
Example fix
// before
rebase.reference("my-branch")?;
rebase.squash_into_preceding(commit_id, None)?; // fails
// after
rebase.squash_into_preceding(commit_id, None)?;
rebase.reference("my-branch")?; Defensive patterns
Strategy: validation
Validate before calling
fn can_squash(steps: &[RebaseStep]) -> bool {
matches!(steps.last(), Some(RebaseStep::Pick(_)))
} Type guard
if !matches!(rebase.last_step(), Some(RebaseStep::Pick(_))) { return Err(anyhow!("fixup requires a preceding pick")); } Prevention
- Only emit squash_into_preceding directly after a pick step
- Emit reference steps after fixups when generating plans
- Unit-test plan generators for reference/fixup ordering
When it happens
Trigger: Calling a step-adding API (e.g. squash_into_preceding) when self.steps.last() is Some(RebaseStep::Reference(..)) — i.e. a reference was registered immediately before the fixup.
Common situations: Building plans programmatically where reference steps and fixups are interleaved; UI-generated plans that place a reference right before a squash; reordering operations that moved a reference after the target commit.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Fixup must have a commit to work on
- Invalid parent delimitation: requested child is not a…
- Invalid parent delimitation: requested parent is not a…
- commit cannot be the base commit
- No rebase steps provided
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/1098767c832a7398.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-rebase/src/lib.rs:188
///
/// Fixup operations:
/// - Must not be a reference step immediately before it
/// - Must not be the first operation
///
/// Reference operations:
/// - The refname must be a valid reference name
fn validate_step(&self, step: &RebaseStep) -> Result<()> {
match step {
RebaseStep::Pick { commit_id, .. } => {
self.assure_unique_step_and_existing_non_base(commit_id, "Picked")?;
}
RebaseStep::SquashIntoPreceding {
commit_id,
new_message: _,
} => {
self.assure_unique_step_and_existing_non_base(commit_id, "Fixup")?;
if matches!(self.steps.last(), Some(RebaseStep::Reference { .. })) {
bail!("Fixup commit must not come after a reference step");
}
if self.steps.is_empty() {
bail!("Fixup must have a commit to work on");
}
}
RebaseStep::Reference(name) => {
if matches!(name, but_core::Reference::Virtual(name) if name.is_empty()) {
return Err(anyhow!(
"Reference step must have a non-empty virtual branch name"
));
}
}
}
Ok(())
}
fn assure_unique_step_and_existing_non_base(
&self,View on GitHub (pinned to 58e5313667)