GitoxideLabs/gitoxide · error
a continuation source is outside the rebase scope
Error message
a continuation source is outside the rebase scope
What it means
Continuation sources are commits where an interrupted rebase may resume; they must all belong to the captured rebase scope. If any continuation-source commit is outside the scope, the state cannot be trusted to resume safely and is rejected.
Solutions
- Delete the stale rebase state and restart the rebase
- Correct the continuation-source entries so they reference in-scope commits
- Ensure the rebase is started and resumed with the same tix version and repository state
Defensive patterns
Strategy: validation
Validate before calling
let sources: HashSet<_> = state.continuation_sources.iter().copied().collect(); let scope: HashSet<_> = state.scope.iter().copied().collect(); assert!(sources.is_subset(&scope), "all continuation sources must be in scope");
Try / catch
match result {
Err(e) if e.to_string().contains("continuation source is outside the rebase scope") => {
// state is inconsistent: discard and restart
}
r => r?,
} Prevention
- Let tix record continuation sources automatically
- Do not append continuation-source entries by hand
- Recreate state after any external history rewrite
When it happens
Trigger: validate_state's final check `!continuation_sources.is_subset(&scope)` fires when the `continuation-source` entries parsed from the state file include ObjectIds absent from `state.scope`. Caused by hand-edited state, truncated writes, or stale state after history changed under it.
Common situations: Editing the state file to add resume points; resuming a rebase after another git operation rewrote commit IDs; copying rebase state across 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
- a continuation fork cannot target an unwritten empty commit
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/b0d1bef135929c2a.
Report an issue: GitHub.
Appendix: source
Thrown at gix-tix/src/edit/todo.rs:824
}
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;
let mut explicit_checkout_reference = None;
let mut command_marker = false;
let mut ref_targets = HashMap::new();View on GitHub (pinned to e73179060b)