gitbutlerapp/gitbutler · error

Fixup must have a commit to work on

Error message

Fixup must have a commit to work on

What it means

A SquashIntoPreceding (fixup) step requires a preceding step to squash into. If self.steps is empty when the fixup is validated, there is no commit to fix up into, so validation rejects the plan. Note this check runs after the reference check, and assure_unique_step_and_existing_non_base already verified the fixup commit itself exists and is unique.

Solutions

  1. Add the target pick step before the fixup: call a step-adding method (e.g. push/commit step) first.
  2. If the fixup should start the plan, convert it into a normal pick instead of squash_into_preceding.
  3. Check plan-generation code so fixups are only emitted after their target commit step.

Example fix

// before
rebase.squash_into_preceding(fixup_id, None)?; // empty plan -> fails
// after
rebase.push(base_commit_id)?;
rebase.squash_into_preceding(fixup_id, None)?;
Defensive patterns

Strategy: validation

Validate before calling

fn has_step_to_fixup(steps: &[RebaseStep]) -> bool {
    !steps.is_empty()
}

Type guard

if steps.is_empty() { return Err(anyhow!("fixup requires a prior step")); }

Prevention

When it happens

Trigger: Calling squash_into_preceding (or building a plan whose first step is a fixup) on a Rebase with no prior steps — e.g. steps empty because base-only construction or prior steps were never added.

Common situations: Starting a plan with a fixup by mistake; logic that clears steps before re-adding them while fixups are queued; UI automation emitting a squash as the first action of a new rebase.

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


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/9a83e30f53fb2382. Report an issue: GitHub.

Appendix: source

Thrown at crates/but-rebase/src/lib.rs:191

    /// - 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,
        commit_id: &gix::oid,
        kind: &str,
    ) -> Result<()> {

View on GitHub (pinned to 58e5313667)