gitbutlerapp/gitbutler · error

Invalid conflict stage '{}'

Error message

Invalid conflict stage '{}'

What it means

During snapshot restore, conflicted index entries are replayed from recorded paths of the form `<stage>/<path>`, where stage is 1 (base), 2 (ours), or 3 (theirs). This bail fires when the first path component is anything else — the recorded index data does not match the expected layout, i.e. the snapshot's index records are corrupt or were written by an incompatible version.

Source

Thrown at crates/gitbutler-oplog/src/oplog.rs:483

fn restore_index_conflicts(index: &mut gix::index::State, conflict_tree: gix::Id) -> Result<()> {
    let conflict_tree = conflict_tree.object()?.try_into_tree()?;
    let mut recorder = gix::traverse::tree::Recorder::default();
    conflict_tree.traverse().depthfirst(&mut recorder)?;

    let mut to_remove = BTreeSet::new();
    for record in &recorder.records {
        if record.mode.is_tree() {
            continue;
        }
        let path = &record.filepath;
        let slash = path
            .find_byte(b'/')
            .context("BUG: expecting <stage>/<path>")?;
        let stage = match &path[..slash] {
            b"1" => Stage::Base,
            b"2" => Stage::Ours,
            b"3" => Stage::Theirs,
            stage => bail!("Invalid conflict stage '{}'", stage.as_bstr()),
        };
        let path = path[slash + 1..].as_bstr();

        index.dangerously_push_entry(
            Default::default(),
            record.oid,
            Flags::from_stage(stage),
            record.mode.into(),
            path,
        );
        to_remove.insert(path);
    }
    index.remove_entries(|_idx, path, entry| {
        entry.flags.stage() == Stage::Unconflicted && to_remove.contains(path)
    });
    index.sort_entries();
    Ok(())
}

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Restore from an earlier snapshot taken before the conflicted state
  2. If the conflict is reproducible, redo the merge and create a fresh snapshot
  3. Report the snapshot id as a bug — restore should not encounter foreign index layouts
Defensive patterns

Strategy: validation

Validate before calling

// while scanning recorded index entries before restoring
if !matches!(&path[..slash], b"1" | b"2" | b"3") {
    return Err(anyhow::anyhow!("snapshot index entry has unknown stage; refusing"));
}

Type guard

fn is_valid_conflict_stage(prefix: &[u8]) -> bool {
    matches!(prefix, b"1" | b"2" | b"3")
}

Try / catch

match oplog.restore(snapshot_id) {
    Err(err) if err.to_string().contains("Invalid conflict stage") => { /* fall back to an earlier snapshot */ }
    other => other,
}

Prevention

When it happens

Trigger: Restoring a snapshot whose index records contain paths whose stage prefix is not `1/`, `2/`, or `3/` (a path with no `/` at all fails earlier with the BUG context error).

Common situations: Snapshot format drift between GitButler versions; partially written snapshot trees; externally manipulated oplog data.

Related errors


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