GitoxideLabs/gitoxide · error

the resolved conflict is outside the rebase scope

Error message

the resolved conflict is outside the rebase scope

What it means

If the state records a resolved conflict commit (`resolved`), that commit must be inside the captured rebase scope. A resolved commit outside the scope means the recorded conflict resolution cannot belong to this rebase, so the state is rejected as inconsistent.

Solutions

  1. Discard the rebase state and restart the rebase to recapture the resolved commit
  2. Update the `resolved` field to the in-scope ObjectId of the actual resolution
  3. Avoid running other history-rewriting git commands while a tix rebase is in progress
Defensive patterns

Strategy: validation

Validate before calling

if let Some(resolved) = state.resolved {
    assert!(scope.contains(&resolved), "resolved commit must be in scope");
}

Try / catch

if let Err(e) = result {
    if e.to_string().contains("resolved conflict is outside the rebase scope") {
        // abandon state and restart the rebase
    }
}

Prevention

When it happens

Trigger: validate_state checks `state.resolved.is_some_and(|id| !scope.contains(&id))`; the bail fires when the `resolved` field of the persisted state holds an ObjectId not present in `state.scope`. Caused by hand-edited state, stale state after external history rewrites, or cross-repo state copies.

Common situations: Resuming an interrupted rebase after a reset/rebase/GC changed commit IDs; manually fixing conflict resolution in the state file.

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 GitoxideLabs/gitoxide@e73179060b (2026-09-08). Data as JSON: /api/errors/9c23a30fe8048bcf. Report an issue: GitHub.

Appendix: source

Thrown at gix-tix/src/edit/todo.rs:821

    }
    for tip in &state.tips {
        repo.find_commit(*tip).context("could not find a recorded rebase tip")?;
    }
    for id in &state.scope {
        let commit = repo
            .find_commit(*id)
            .context("could not find a recorded scope commit")?;
        let parent = commit
            .parent_ids()
            .next()
            .map(gix::Id::detach)
            .context("a recorded scope commit has no parent")?;
        if parent != state.base && !scope.contains(&parent) && !continuation_sources.contains(id) {
            anyhow::bail!("a recorded scope commit is disconnected from the rebase base");
        }
    }
    if state.resolved.is_some_and(|id| !scope.contains(&id)) {
        anyhow::bail!("the resolved conflict is outside the rebase scope");
    }
    if !continuation_sources.is_subset(&scope) {
        anyhow::bail!("a continuation source is outside the rebase scope");
    }
    Ok(())
}

pub(crate) fn parse(repo: &gix::Repository, edited: &[u8]) -> Result<Option<Parsed>> {
    repo.head()?.id().context("rebase todos require a born HEAD")?;
    let input = std::str::from_utf8(edited).context("the rebase todo is not UTF-8")?;
    let Some(mut state) = parse_state(repo, input)? else {
        return Ok(None);
    };
    let scope: HashSet<_> = state.scope.iter().copied().collect();
    let mut picked = HashMap::<ObjectId, usize>::new();
    let mut steps = Vec::<rebase::PlanStep>::new();
    let mut cursor = None;
    let mut checkout_target = None;

View on GitHub (pinned to e73179060b)