GitoxideLabs/gitoxide · error
the rebase state repeats its HEAD ref
Error message
the rebase state repeats its HEAD ref
What it means
`parse_state` allows the `head-ref` field only once per rebase state file. When a second `head-ref` line is parsed, the `head_ref.replace(...)` returns `Some`, indicating a repeat, and parsing aborts. This guards the single-valued field against silent overwrite of the recorded HEAD reference.
Solutions
- Remove the duplicate `head-ref` line, keeping exactly one valid fully-qualified ref name.
- Restart the rebase/edit operation to regenerate clean state.
- Audit any scripts or automation that modify the state file to ensure they replace rather than append fields.
Example fix
// before (state file) head-ref "refs/heads/main" head-ref "refs/heads/other" // after head-ref "refs/heads/main"
Defensive patterns
Strategy: validation
Validate before calling
fn state_file_is_clean(text: &str) -> bool {
text.lines().filter(|l| l.starts_with("head-ref ")).count() <= 1
} Try / catch
match todo::parse(state_text) {
Ok(state) => apply(state),
Err(e) if e.to_string().contains("repeats its HEAD ref") => {
eprintln!("corrupt state: duplicate head-ref; keep exactly one");
}
Err(e) => return Err(e),
} Prevention
- Use replace-not-append semantics when updating head-ref programmatically.
- Run a duplicate-key lint over generated state files.
- Avoid merging state files from separate sessions.
When it happens
Trigger: `parse_state` (gix-tix/src/edit/todo.rs:715) reading a state file with two `head-ref` lines — from hand edits, corrupted state, or tooling that appends fields.
Common situations: Merged state files from conflicting edits; duplicated lines from faulty scripts or editors; copying state between worktrees and concatenating content.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- the rebase state repeats marker-required
- the rebase state repeats checkout-allowed
- the rebase state repeats its resolved conflict
- the recorded HEAD ref has trailing data
- a captured ref name has trailing data
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/9a511b64458ae55f.
Report an issue: GitHub.
Appendix: source
Thrown at gix-tix/src/edit/todo.rs:715
anyhow::bail!("the rebase state repeats marker-required");
}
}
"checkout-allowed" => {
if checkout_allowed.replace(value.parse()?).is_some() {
anyhow::bail!("the rebase state repeats checkout-allowed");
}
}
"head-ref" => {
let encoded = value.as_bytes().as_bstr();
let (name, consumed) = gix::quote::ansi_c::undo(encoded)
.map_err(gix::Exn::into_error)
.context("could not unquote the recorded HEAD ref")?;
if !encoded[consumed..].trim().is_empty() {
anyhow::bail!("the recorded HEAD ref has trailing data");
}
let name = gix::refs::FullName::try_from(name.as_ref()).context("the recorded HEAD ref is invalid")?;
if head_ref.replace(name).is_some() {
anyhow::bail!("the rebase state repeats its HEAD ref");
}
}
"edit-refs" => edit_refs = value.parse()?,
"ref" => {
let (old, value) = value.split_once(' ').context("a captured ref has no target")?;
let (target, value) = value.split_once(' ').context("a captured ref has no follow mode")?;
let old = (old != "-").then(|| ObjectId::from_hex(old.as_bytes())).transpose()?;
let target = ObjectId::from_hex(target.as_bytes())?;
let (follows_tip, value) = value.split_once(' ').context("a captured ref has no name")?;
let follows_tip = follows_tip.parse()?;
let (editable, name) = value
.split_once(' ')
.and_then(|(editable, name)| editable.parse::<bool>().ok().map(|editable| (editable, name)))
.unwrap_or((false, value));
let encoded_name = name.as_bytes().as_bstr();
let (name, consumed) = gix::quote::ansi_c::undo(encoded_name)
.map_err(gix::Exn::into_error)
.context("could not unquote a captured ref name")?;View on GitHub (pinned to e73179060b)