GitoxideLabs/gitoxide · error
the recorded HEAD ref has trailing data
Error message
the recorded HEAD ref has trailing data
What it means
After unquoting the `head-ref` value with `gix::quote::ansi_c::undo`, the parser checks that nothing meaningful remains after the quoted reference name (`encoded[consumed..].trim()`). Trailing data after the ref name means the `head-ref` line is malformed, so parsing aborts. This catches corrupted or hand-mangled ref entries before they are turned into a `gix::refs::FullName`.
Solutions
- Edit the `head-ref` line so it contains exactly one (optionally C-quoted) fully-qualified ref name and no trailing tokens.
- Delete the `head-ref` line entirely if it is optional and the head is detached — the parser treats it as absent.
- Regenerate the state file by restarting the operation.
Example fix
// before (state file) head-ref "refs/heads/main" extra-token // after head-ref "refs/heads/main"
Defensive patterns
Strategy: validation
Validate before calling
fn head_ref_line_is_clean(line: &str) -> bool {
line.strip_prefix("head-ref ")
.map(|v| v.split_whitespace().count() == 1)
.unwrap_or(true)
} Try / catch
match todo::parse(state_text) {
Ok(state) => apply(state),
Err(e) if e.to_string().contains("HEAD ref has trailing data") => {
eprintln!("fix the head-ref line: one C-quoted fully-qualified ref only");
}
Err(e) => return Err(e),
} Prevention
- C-quote ref names containing spaces or special bytes when writing state.
- Never append extra tokens to head-ref lines when editing.
- Diff state files against the tool-generated format before saving.
When it happens
Trigger: `parse_state` (gix-tix/src/edit/todo.rs:711) sees a `head-ref` line whose value contains extra tokens after the (possibly C-quoted) ref name, e.g. `head-ref "refs/heads/main" extra` or `head-ref refs/heads/main garbage`.
Common situations: Manual edits leaving stray characters; copy-paste of ref lines with extra whitespace-separated tokens; corruption from interrupted writes; older/newer state formats that included extra data.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- a captured ref name has trailing data
- the rebase state repeats marker-required
- the rebase state repeats checkout-allowed
- the rebase state repeats its HEAD ref
- the rebase state repeats its resolved conflict
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/48785cf924649866.
Report an issue: GitHub.
Appendix: source
Thrown at gix-tix/src/edit/todo.rs:711
"tip" => tips.push(ObjectId::from_hex(value.as_bytes())?),
"scope" => scope.push(ObjectId::from_hex(value.as_bytes())?),
"marker-required" => {
if marker_required.replace(value.parse()?).is_some() {
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));View on GitHub (pinned to e73179060b)