GitoxideLabs/gitoxide · error
the HEAD pin must point to a local branch
Error message
the HEAD pin must point to a local branch
What it means
The remembered HEAD pin's target is not a symbolic reference to a local branch: either `try_name()` returned None (detached HEAD) or the name does not start with `refs/heads/`. Attaching time-travel state requires HEAD to be anchored to a local branch so it can be re-attached later. The library throws this when snapshotting/remembering the current branch.
Solutions
- Check out a local branch first (`git checkout -b <name>` to create one from the current commit).
- Ensure HEAD resolves to a `refs/heads/*` ref before starting the time-travel/attach flow.
- If on a remote-tracking state, create a local branch tracking it.
Example fix
// before (detached HEAD) $ git checkout 1a2b3c4 # attach fails // after $ git checkout -b work # local branch, attach succeeds
Defensive patterns
Strategy: validation
Validate before calling
let head = repo.head()?;
let is_local_branch = !head.is_detached()
&& head.referent_name().map(|n| n.as_bstr().starts_with(b"refs/heads/")).unwrap_or(false);
if !is_local_branch { return Err(anyhow!("must be on a local branch")); } Type guard
fn on_local_branch(head: &gix::reference::Reference<'_>) -> bool {
!head.is_detached()
&& head
.referent_name()
.map(|n| n.as_bstr().starts_with(b"refs/heads/"))
.unwrap_or(false)
} Try / catch
match result {
Err(e) if e.to_string().contains("must point to a local branch") => {
eprintln!("detached HEAD; create/checkout a local branch first");
}
other => other?,
} Prevention
- Never run the flow from a detached-HEAD checkout (common in CI).
- Create a local branch when starting from a remote-tracking or tag state.
- Assert HEAD is symbolic to refs/heads/* at workflow start.
When it happens
Trigger: Calling remembered_branch (via attach_reporting) while HEAD is detached, or while HEAD points at something other than a local branch (e.g. `refs/remotes/origin/main`, a tag, or another pseudo-ref).
Common situations: Operating in a repo checked out at a bare commit (detached HEAD, e.g. after `git checkout <sha>` or CI checkouts); HEAD pointing to a remote-tracking branch; a pin captured on a tag.
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
- attaching requires detached HEAD
- a detached checked-out root commit cannot be removed
- cannot split with unresolved conflicts
- splitting requires both staged and worktree changes
- changes can only be stashed at the current HEAD
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/957a5a935242d081.
Report an issue: GitHub.
Appendix: source
Thrown at gix-tix/src/edit/time_travel.rs:498
} else {
notice = format!("{notice}; saved {}", pin_label(&provisional));
}
}
Ok((Some(notice), ref_changes))
}
fn remembered_branch(repository: &gix::Repository) -> Result<RememberedBranch> {
let pin = history::all_pins(repository)?
.into_iter()
.find(history::Pin::is_head)
.context("attaching requires a valid HEAD pin")?;
let branch = pin
.target
.try_name()
.context("the HEAD pin must point to a local branch")?
.to_owned();
if !branch.as_bstr().starts_with(b"refs/heads/") {
anyhow::bail!("the HEAD pin must point to a local branch");
}
Ok(RememberedBranch {
branch,
branch_tip: pin.id,
})
}
fn validate_attach(repository: &gix::Repository, head_id: ObjectId, remembered: &RememberedBranch) -> Result<()> {
let head = repository.head().context("could not read HEAD before attaching")?;
if !head.is_detached() || head.id().map(gix::Id::detach) != Some(head_id) {
anyhow::bail!("HEAD changed while preparing to attach");
}
drop(head);
let pin = history::all_pins(repository)?
.into_iter()
.find(history::Pin::is_head)
.context("the HEAD pin disappeared while preparing to attach")?;
if pin.target.try_name() != Some(remembered.branch.as_ref()) || pin.id != remembered.branch_tip {View on GitHub (pinned to e73179060b)