gitbutlerapp/gitbutler · error
HEAD does not point to a local branch
Error message
HEAD does not point to a local branch
What it means
current_branch_targets in crates/but-agentlog/src/environment.rs requires HEAD to point to a local branch (refs/heads/...). If HEAD's referent is any other ref (e.g. refs/remotes/... or a custom ref), it bails with "HEAD does not point to a local branch". It also separately rejects the GitButler workspace ref.
Solutions
- Attach HEAD to a local branch: `git switch <local-branch>` so the referent is refs/heads/<branch>.
- Create a local branch tracking the remote: `git switch -c <branch> --track origin/<branch>`.
- If GitButler workspace mode is active, pause/exit it or run the snapshot against a non-workspace branch.
Example fix
// before git checkout origin/main # HEAD on refs/remotes/... // after git switch -c main --track origin/main
Defensive patterns
Strategy: validation
Validate before calling
ref=$(git symbolic-ref -q HEAD) && case "$ref" in refs/heads/*) ;; *) echo "HEAD not on a local branch"; exit 1 ;; esac
Try / catch
match result {
Err(e) if e.to_string().contains("HEAD does not point to a local branch") => {
// attach to a local branch and retry
}
r => r?,
} Prevention
- Checkout remote refs with --track to create a local branch.
- Avoid symref tricks that point HEAD at refs/remotes or custom namespaces.
- Exit GitButler workspace mode or pick a non-workspace branch before branch-target snapshots.
When it happens
Trigger: Calling current_branch_targets while HEAD is attached to a remote-tracking ref or non-branch ref (e.g. `git checkout origin/main` detached-attach states, custom symrefs), or to the GitButler workspace ref (which raises a different bail in the same function).
Common situations: Tooling that checked out a remote ref or tag-like ref; automation that re-points HEAD at custom refs; running agentlog inside a repo managed by GitButler's workspace mode with HEAD on the workspace ref.
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
- Can only delete local branches under refs/heads, got
- failed to determine remote for non remote-tracking branch
- another pre-commit hook is already using the repository…
- askpass broker must be initialized
- Branch ' ' already exists
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/5461ed7bafd7c261.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-agentlog/src/environment.rs:389
Err(error) => match current_branch_targets(repo) {
Ok(fallback) if !fallback.is_empty() => Ok(WorkspaceObservation {
stacks: Vec::new(),
observed_targets: fallback,
error_kind: Some(SnapshotErrorKind::Workspace),
}),
_ => Err(error),
},
}
}
fn current_branch_targets(repo: &gix::Repository) -> anyhow::Result<ObservedTargets> {
let head = repo.head()?;
let Some(ref_name) = head.referent_name() else {
anyhow::bail!("HEAD is detached");
};
let full_ref_name = ref_name.to_string();
if !full_ref_name.starts_with("refs/heads/") {
anyhow::bail!("HEAD does not point to a local branch");
}
if is_workspace_ref_name(ref_name) {
anyhow::bail!("HEAD points to the GitButler workspace branch");
}
let branch = BranchTarget {
key: format!("ref:{full_ref_name}"),
name: ref_name.shorten().to_string(),
};
// Derive the PR association from the forge review cache rather than stored
// metadata. Synthesize a metadata entry carrying the resolved number so a
// branch with a cached PR is reported even without stored branch metadata.
let reviews = match resolve_branch_pr(repo, ref_name, &forge_prs_by_head(repo)) {
Some(pull_request) => {
let mut metadata = Branch::default();
metadata.review.pull_request = Some(pull_request);
review_targets(&branch.key, Some(&metadata))
}
None => Vec::new(),View on GitHub (pinned to 58e5313667)