gitbutlerapp/gitbutler · error
HEAD is detached
Error message
HEAD is detached
What it means
current_branch_targets in crates/but-agentlog/src/environment.rs resolves the current branch from HEAD to build ObservedTargets. If repo.head() returns a HEAD that is not a symbolic reference (no referent), it bails with "HEAD is detached" because branch targets cannot be determined from a detached HEAD.
Solutions
- Check out a local branch first: `git switch <branch>` (or `git switch -c <branch>` to keep the commits).
- Skip the branch-target snapshot when HEAD is detached: test with `git symbolic-ref -q HEAD` before invoking.
- Extend the caller to handle detached HEAD explicitly (record the commit id instead of a branch target).
Example fix
// before but agentlog ... # fails in detached HEAD // after git switch -c work-from-detached but agentlog ...
Defensive patterns
Strategy: fallback
Validate before calling
# detect detached HEAD before running
git symbolic-ref -q HEAD >/dev/null 2>&1 || { echo "HEAD is detached"; exit 1; } Try / catch
match result {
Err(e) if e.to_string().contains("HEAD is detached") => {
// fall back to commit-id-based snapshot or skip
}
r => r?,
} Prevention
- Run agent tooling on a branch, not a bare commit checkout.
- In CI, create a branch from the checkout SHA before running snapshots.
- Check for rebase/checkout states that leave HEAD detached.
When it happens
Trigger: Calling current_branch_targets (via workspace_snapshot_with_current_branch_fallback) while the repo is in detached-HEAD state, e.g. after `git checkout <sha>` or a rebase in progress.
Common situations: Agent tooling runs inside a CI checkout pinned to a commit; a user checked out a tag or commit for inspection; mid-rebase states leave HEAD detached.
Understand the failure class
Background: "git command failed": what it means when a tool shells out to git and git exits non-zero — this error's family across 21 libraries.
Related errors
- another pre-commit hook is already using the repository…
- askpass broker must be initialized
- Branch not found
- Branch ` ` is pushed, but its remote ancestry does not…
- Branch not found
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/112196a0785f220f.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-agentlog/src/environment.rs:385
observation.observed_targets = fallback;
}
Ok(observation)
}
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();View on GitHub (pinned to 58e5313667)