GitoxideLabs/gitoxide · error
a fork target must be picked before it is used
Error message
a fork target must be picked before it is used
What it means
A fork heading names a target commit; if that target belongs to the rebase scope it must already have been picked by an earlier (lower in the file, since parsing is bottom-up) plan step, so the fork can attach to that step. Using an in-scope fork target before it is picked would create a plan referencing a nonexistent step, so it is rejected.
Solutions
- Move the fork heading below (in file order: after) the section that picks its target commit
- Change the fork target to a commit outside the rebase scope (an existing commit) if it should not attach to a plan step
- Restore the pick line for the target commit in the preceding section
Example fix
// before (fork targets in-scope commit before it is picked) ──fork def5678── pick 2222222 ──fork abc1234── pick 1111111 // after (pick the target first, then fork onto it) ──fork abc1234── pick 1111111 ──fork def5678── pick 2222222
Defensive patterns
Strategy: validation
Validate before calling
// fork targets inside the scope must already have a pick step above them
for fork in fork_targets(edited) {
if scope.contains(&fork.id) && !picked_before(fork, edited) {
return Err(format!("fork target {} must be picked first", fork.id));
}
} Try / catch
if let Err(e) = parse(repo, edited) {
if e.to_string().contains("must be picked before it is used") {
// prompt user to reorder the fork section
}
} Prevention
- Keep fork headings adjacent to the section that picks their target
- Reorder whole sections, not individual fork headings
- Consult the generated todo comments describing ordering rules
When it happens
Trigger: `parse` bails when a fork heading's resolved commit `id` is in `scope` but not in the `picked` map at that point (not `PlanParent::Step`). Happens when a user reorders fork sections so a fork targeting a scoped commit appears before the section that picks that commit.
Common situations: Manually reordering fork blocks in the todo editor; moving a fork heading to the wrong position while restructuring; deleting the pick step a fork depended on.
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
- a fork section contains no commits
- a reference is placed more than once
- the rebase todo cannot select a checkout without a worktree
- the rebase todo contains more than one @ reference
- the rebase todo contains more than one @ command
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/8075bbfc83aa3ab8.
Report an issue: GitHub.
Appendix: source
Thrown at gix-tix/src/edit/todo.rs:886
let target = line
.trim_matches('─')
.trim()
.strip_prefix("fork ")
.context("a fork separator needs a fork ID")?;
if sections > 0 && !section_has_commit {
anyhow::bail!("a fork section contains no commits");
}
let id = resolve_commit(
repo,
target
.split_whitespace()
.next()
.context("a fork heading needs a commit ID")?,
)?;
cursor = Some(if let Some(index) = picked.get(&id) {
rebase::PlanParent::Step(*index)
} else if scope.contains(&id) {
anyhow::bail!("a fork target must be picked before it is used");
} else {
rebase::PlanParent::Existing(id)
});
sections += 1;
section_has_commit = false;
section_last_step = None;
continue;
}
if line.starts_with('(') && line.ends_with(')') {
let target = cursor.context("a reference line must follow a fork or command")?;
for (marked, value) in parse_ref_line(line)? {
let name = resolve_ref_name(repo, &mut state.expected_refs, value.as_bstr())?;
if ref_targets.insert(name.clone(), target).is_some() {
anyhow::bail!("a reference is placed more than once");
}
if marked {
if !state.checkout_allowed || repo.workdir().is_none() {
anyhow::bail!("the rebase todo cannot select a checkout without a worktree");View on GitHub (pinned to e73179060b)