gitbutlerapp/gitbutler · error

Stack has no ID

Error message

Stack has no ID

What it means

Immediately after finding the matching stack in `absorption_plan_with_perm`, the code requires `stack.id` to be `Some`. A matched stack with `None` id means in-memory workspace data was built without persisting/assigning a stack identifier — an internal invariant breach rather than bad user input. The error surfaces as "Stack has no ID".

Solutions

  1. Re-read/refresh workspace metadata from the database so stack ids are populated, then retry.
  2. Run `but status` / restart the operation to force a fresh workspace build.
  3. Check for mixed tool versions touching the same workspace; upgrade the CLI/app to matching versions.
  4. If persistent, report as a bug — a persisted applied stack should always carry an id.

Example fix

// before
let plan = absorption_plan(ctx, branch)?; // may hit Stack has no ID
// after
ctx.reload_workspace()?; // refresh persisted metadata (ids present)
let plan = absorption_plan(ctx, branch)?;
Defensive patterns

Strategy: try-catch

Validate before calling

// a persisted applied stack must carry an id; surface anomalies early
if let Some(stack) = ws.stacks.iter().find(|s| matches_branch(s)) {
    if stack.id.is_none() {
        eprintln!("BUG: matched stack has no id; workspace metadata may be corrupt");
    }
}

Try / catch

match absorption_plan(ctx, branch) {
    Err(e) if e.to_string().contains("Stack has no ID") => {
        ctx.reload_workspace()?; // refresh persisted metadata and retry once
        absorption_plan(ctx, branch)?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling absorption_plan against a workspace whose stack entries were constructed without an id (e.g. un-persisted stacks from a stale metadata read, partially migrated workspace state, or a bug in workspace building where `stack.id` was never set).

Common situations: Corrupted or mid-migration GitButler workspace metadata; operating on workspace data produced by an older/newer tool version with a different persistence schema; transient in-memory state not yet written to the DB.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at crates/but-api/src/legacy/absorb.rs:147

            // Get the stack ID for this branch
            let workspace = crate::legacy::workspace::head_info(ctx)?;

            // Find the stack that contains this branch
            let stack = workspace
                .stacks
                .iter()
                .find(|stack| {
                    stack.segments.iter().any(|segment| {
                        segment
                            .ref_info
                            .as_ref()
                            .is_some_and(|ri| ri.ref_name.shorten() == branch_name.as_bytes())
                    })
                })
                .ok_or_else(|| anyhow::anyhow!("Branch not found: {branch_name}"))?;

            let stack_id = stack.id.ok_or_else(|| anyhow::anyhow!("Stack has no ID"))?;

            // Filter assignments to just this stack
            let candidates: Vec<AbsorbCandidate> = all_assignments
                .into_iter()
                .filter(|a| a.stack_id == Some(stack_id))
                .map(Into::into)
                .collect();

            if candidates.is_empty() {
                anyhow::bail!("No uncommitted changes assigned to branch: {branch_name}");
            }

            (candidates, dependencies)
        }
        AbsorptionTarget::TreeChanges {
            changes,
            assigned_stack_id,
        } => {

View on GitHub (pinned to 58e5313667)