GitoxideLabs/gitoxide · error
a captured ref does not logically point into the rebase…
Error message
a captured ref does not logically point into the rebase scope
What it means
During rebase state validation, each recorded expected ref must point to a commit that is either inside the captured rebase scope, the base commit, or the onto target. If a ref's target is none of these, the state file is logically inconsistent (e.g. corrupted or hand-edited) and tix refuses to restore the rebase. This prevents the rebase from moving refs to commits unrelated to the in-progress operation.
Solutions
- Delete the stale rebase state and restart the rebase so state is regenerated from real refs
- Inspect the recorded expected_refs in the state file and correct ref targets to commits within the scope, base, or onto
- Check that no concurrent git/tix operation (reset, rebase, GC) modified the refs captured by the in-progress rebase
Example fix
// before (hand-edited state) ref main = 1a2b3c4... (commit outside scope) // after abandon the state (`tix` restart the rebase) so expected_refs are recaptured from actual refs
Defensive patterns
Strategy: validation
Validate before calling
// before resuming, verify every expected ref target is in scope/base/onto
for reference in &state.expected_refs {
assert!(scope.contains(&reference.target)
|| reference.target == state.base
|| reference.target == state.onto,
"ref {} points outside rebase scope", reference.name);
} Try / catch
match result {
Err(e) if e.to_string().contains("does not logically point into the rebase scope") => {
// discard state, prompt user to restart the rebase
}
r => r?,
} Prevention
- Do not hand-edit tix rebase state files
- Avoid running git reset/rebase/GC while a tix rebase is in progress
- Restart the rebase rather than patching stale state
When it happens
Trigger: Calling the todo/state parsing path (parse -> parse_state -> validate_state) on a persisted rebase state whose `expected_refs` contain a reference whose target commit is outside `state.scope`, `state.base`, and `state.onto`. Typically caused by manual editing of the state file, corruption, or stale state after history was rewritten externally.
Common situations: Hand-editing or script-mangling the tix rebase state file; resuming a rebase after a GC or reset removed and rewrote commits so recorded ref targets no longer match; copying rebase state between repositories.
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
- the root of a repeated rebase must be pending
- the parent of a repeated rebase must not be pending
- the current checkout has a pending rebase; time-travel to…
- has a pending rebase
- the rebase state contains duplicate scope commits
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/06744d3f1b8fd9f7.
Report an issue: GitHub.
Appendix: source
Thrown at gix-tix/src/edit/todo.rs:793
repo.find_commit(state.base)
.context("could not find the recorded rebase base")?;
repo.find_commit(state.onto)
.context("could not find the recorded rebase target")?;
let scope: HashSet<_> = state.scope.iter().copied().collect();
let continuation_sources: HashSet<_> = state.continuation_sources.iter().copied().collect();
if scope.len() != state.scope.len() {
anyhow::bail!("the rebase state contains duplicate scope commits");
}
if state.tips.iter().copied().collect::<HashSet<_>>().len() != state.tips.len() {
anyhow::bail!("the rebase state contains duplicate tips");
}
let mut refs = HashSet::new();
for reference in &state.expected_refs {
if !refs.insert(reference.name.as_bstr()) {
anyhow::bail!("the rebase state contains duplicate refs");
}
if !scope.contains(&reference.target) && reference.target != state.base && reference.target != state.onto {
anyhow::bail!("a captured ref does not logically point into the rebase scope");
}
}
if let Some(name) = &state.head_ref
&& !state
.expected_refs
.iter()
.any(|reference| reference.editable && reference.name == *name)
{
anyhow::bail!("the recorded HEAD ref is not editable");
}
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 = commitView on GitHub (pinned to e73179060b)