GitoxideLabs/gitoxide · error
the last fork section contains no commits
Error message
the last fork section contains no commits
What it means
When the parsed todo contains more than one fork section, the final section must contain at least one commit command (`pick`, `empty`, `squash`, or a reference line). `parse` throws this error when the todo ends with a `fork` heading that is never followed by any command, which would leave the plan with an unusable trailing section.
Solutions
- Add at least one `pick`, `empty`, `squash`, or `(ref…)` line below the final fork heading.
- Delete the trailing fork heading entirely if its section is intentionally empty and it is the last section.
- Merge the trailing empty section into the previous section by removing the extra fork separator.
Example fix
// before ──── fork aaa1 base ──── pick bbb2 ... ──── fork ccc3 tip ──── // after (heading removed) ──── fork aaa1 base ──── pick bbb2 ...
Defensive patterns
Strategy: validation
Validate before calling
// Rust: ensure the last fork section is non-empty before submitting the todo
let editable: Vec<&str> = text.lines()
.filter(|l| !l.trim().is_empty() && !l.trim().starts_with("# ") && !l.contains("<!--"))
.collect();
let last_is_bare_fork = editable.last()
.map(|l| l.starts_with('─') && l.trim_matches('─').trim().starts_with("fork "))
.unwrap_or(false);
if last_is_bare_fork {
return Err("last fork section contains no commits");
} Try / catch
match parse_plan(&repo, text) {
Ok(plan) => apply(plan),
Err(e) if e.to_string().contains("last fork section contains no commits") => {
eprintln!("Add a command under the final fork heading or delete it.");
Err(e)
}
Err(e) => return Err(e),
} Prevention
- When deleting trailing commits, delete their fork heading too.
- Check the bottom of the todo before saving: the last line should be a command, not a fork heading.
- Avoid duplicate fork separators at the end of the file.
When it happens
Trigger: Editing the todo so the last `──── fork <id> ────` heading has no `pick`/`empty`/`squash`/reference lines beneath it (e.g. deleting all trailing picks, or leaving a stray duplicated fork heading at the end).
Common situations: A user removes the trailing lines of the todo while pruning history but keeps the fork heading; a scripted todo generator appends a fork marker without emitting the section's commits; the editor truncated the file.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- the rebase todo contains more than one @ command
- the rebase todo has no fork heading
- an editable commit is not connected to the selected base
- a fork section contains no commits
- a fork target must be picked before it is used
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/f588a544ed0b444c.
Report an issue: GitHub.
Appendix: source
Thrown at gix-tix/src/edit/todo.rs:1018
commit,
squash: Vec::new(),
});
cursor = Some(rebase::PlanParent::Step(index));
section_last_step = Some(index);
if marked {
let target = rebase::PlanParent::Step(index);
if checkout_target.is_some_and(|checkout| checkout != target) {
anyhow::bail!("the @ command and @ reference point to different results");
}
checkout_target = Some(target);
}
section_has_commit = true;
}
if sections == 0 {
anyhow::bail!("the rebase todo has no fork heading");
}
if sections > 1 && !section_has_commit {
anyhow::bail!("the last fork section contains no commits");
}
if state.marker_required && checkout_target.is_none() {
anyhow::bail!("the current checkout marker must be retained");
}
let checkout_reference = match (checkout_target, explicit_checkout_reference) {
(Some(target), Some((name, reference_target))) => {
if target != reference_target {
anyhow::bail!("the @ command and @ reference point to different results");
}
Some(name)
}
(None, Some(_)) => anyhow::bail!("an @ reference requires an @ command at the same result"),
(Some(target), None) => state
.head_ref
.take()
.filter(|name| ref_targets.get(name) == Some(&target)),
(None, None) => None,
};View on GitHub (pinned to e73179060b)