gitbutlerapp/gitbutler · critical

Missing stack '{stack_id}' for head '{name}'

Error message

Missing stack '{stack_id}' for head '{name}'

What it means

Thrown during legacy metadata migration in crates/but-meta/src/legacy/storage.rs: while converting the old virtual_branches.json heads list, a head entry references a stack_id that does not exist in the stacks map built from the same file. The migration refuses to attach the head to a nonexistent stack, which means the persisted legacy state is internally inconsistent (corruption or a hand-edited file).

Source

Thrown at crates/but-meta/src/legacy/storage.rs:511

            },
        );
    }

    for head in heads {
        let VbStackHead {
            stack_id,
            position: _, // previously set based on vec position
            name,
            head_sha,
            pr_number,
            archived,
            review_id,
        } = head;
        let stack_id = StackId::from_str(stack_id)
            .with_context(|| format!("Invalid stack id '{stack_id}'"))?;
        let stack = branches
            .get_mut(&stack_id)
            .ok_or_else(|| anyhow!("Missing stack '{stack_id}' for head '{name}'"))?;
        stack.heads.push(StackBranch {
            head: gix::ObjectId::from_str(head_sha)
                .with_context(|| format!("Invalid head sha '{head_sha}' on '{name}'"))?,
            name: name.clone(),
            pr_number: pr_number
                .map(usize::try_from)
                .transpose()
                .with_context(|| {
                    format!(
                        "Invalid pr_number '{}' on stack '{stack_id}'",
                        pr_number.unwrap_or_default(),
                    )
                })?,
            archived: *archived,
            review_id: review_id.clone(),
        });
    }

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Restore the workspace metadata from backup (the file is versioned; check history or the automatic snapshots) and retry.
  2. If no backup exists, archive the repo's .gitbutler metadata aside and let GitButler reinitialize the workspace, then recreate virtual branches.
  3. Inspect virtual_branches.json: cross-check every heads[].stack_id against the stacks[].id values to locate the orphan.
  4. Report the file (sanitized) to GitButler — an internally inconsistent file usually indicates an old writer bug worth tracking.
Defensive patterns

Strategy: validation

Validate before calling

// Before migrating legacy metadata, cross-check head -> stack references
use std::collections::HashSet;

fn heads_reference_existing_stacks(heads: &[LegacyHead], stacks: &[LegacyStack]) -> bool {
    let ids: HashSet<String> = stacks.iter().map(|s| s.stack_id.clone()).collect();
    heads.iter().all(|h| ids.contains(&h.stack_id))
}

Type guard

fn legacy_metadata_is_consistent(
    heads: &[HeadEntry],
    stack_ids: &std::collections::HashSet<String>,
) -> bool {
    heads.iter().all(|h| stack_ids.contains(&h.stack_id))
}

Try / catch

match migrate_legacy_storage(&vb_json) {
    Ok(v2) => Ok(v2),
    Err(e) if e.to_string().contains("Missing stack") => {
        // quarantine the file, restore from backup/snapshot, then retry
        Err(e)
    },
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Loading a legacy virtual_branches.json whose heads array contains a head with stack_id absent from the stacks section; truncated or hand-edited metadata; files written by a buggy older version that desynchronized stacks and heads.

Common situations: Users upgrading from very old GitButler versions with damaged workspace metadata; manually edited or merged .gitbutler metadata files; a crash during an old metadata write leaving stacks and heads out of sync.

Related errors


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