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

  1. Attach HEAD to a local branch: `git switch <local-branch>` so the referent is refs/heads/<branch>.
  2. Create a local branch tracking the remote: `git switch -c <branch> --track origin/<branch>`.
  3. 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

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


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)