gitbutlerapp/gitbutler · error
Operation not possible while HEAD is detached
Error message
Operation not possible while HEAD is detached
What it means
Thrown by integrate_upstream_with_hints when HEAD is detached such that the workspace is Ad-hoc and has no ref name. Upstream integration operates on a named workspace/stack ref, so with no branch checked out there is nothing to integrate into. The library refuses rather than guessing a ref.
Solutions
- Check out a branch first (git switch <branch>) before integrating upstream changes.
- In code, detect detached HEAD (workspace.kind == AdHoc && ref_name().is_none()) and fail fast with a clear user instruction.
- For CI, create a branch at the checked-out commit before invoking integration.
Example fix
# before (detached) git checkout 1a2b3c4 but integrate # fails # after git switch -c work-branch # or: git switch main but integrate
Defensive patterns
Strategy: validation
Validate before calling
fn can_integrate(workspace: &Workspace) -> bool {
!(workspace.kind == but_graph::workspace::WorkspaceKind::AdHoc
&& workspace.ref_name().is_none())
} Type guard
fn workspace_ref(ws: &Workspace) -> Option<&gix::refs::FullNameRef> {
(ws.kind != WorkspaceKind::AdHoc).then_some(()).and(ws.ref_name())
} Try / catch
if let Err(e) = integrate_upstream(...) {
if e.to_string().contains("HEAD is detached") {
eprintln!("checkout a branch before integrating");
} else { return Err(e); }
} Prevention
- Detect detached HEAD at app startup and prompt the user to create/switch to a branch
- In CI, create a branch at the fetched commit before any integration step
- Check workspace.kind and ref_name() before invoking upstream integration APIs
When it happens
Trigger: Calling integrate_upstream (or its hint-based variant) while the repository is in detached-HEAD state (e.g. after checkout of a commit), producing WorkspaceKind::AdHoc with workspace.ref_name() == None.
Common situations: CI pipelines that check out a raw commit SHA; users who ran `git checkout <sha>` or a bisect; tools that create worktrees in detached state and then attempt stack integration.
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
- Cannot unapply a branch from an ad-hoc detached workspace
- Ad-hoc (single-branch) branch moves are not supported…
- Branch ' ' cannot be created: the target commit ( ) already…
- BranchNotFound
- BUG: failed to apply head ref
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/266a6fda66c99fd1.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-workspace/src/upstream_integration.rs:187
///
/// With `single_branch_mode`, a managed workspace whose applied stacks were all integrated is
/// replaced by a checked-out canned branch at the target tip. Otherwise the emptied managed
/// workspace stays checked out, reparented onto the target.
#[allow(clippy::too_many_arguments)]
pub fn integrate_upstream_with_hints<'ws, 'meta, M: RefMetadata>(
workspace: &'ws mut but_graph::Workspace,
meta: &'meta mut M,
project_meta: ProjectMeta,
repo: &gix::Repository,
db: &'meta mut but_db::DbHandle,
updates: Vec<BottomUpdate>,
review_hints: &[ReviewIntegrationHint],
single_branch_mode: bool,
) -> Result<IntegrateUpstreamOutcome<'ws, 'meta, M>> {
if matches!(workspace.kind, but_graph::workspace::WorkspaceKind::AdHoc)
&& workspace.ref_name().is_none()
{
bail!("Operation not possible while HEAD is detached");
}
let mut ws_meta = workspace.metadata.clone();
let target_sha = project_meta
.target_commit_id
.context("Cannot update a workspace without a target sha")?;
let target_ref = workspace
.target_ref
.clone()
.context("Cannot update a workspace with no target ref")?;
let target_ref_commit = repo.find_reference(&target_ref.ref_name)?.id();
let entrypoint = workspace.graph.entrypoint()?;
let head_commit = entrypoint
.commit()
.context("Cannot update workspace without head commit")?;
let head_commit = repo.find_commit(head_commit.id)?;
let head_commit_id = head_commit.id;View on GitHub (pinned to 58e5313667)