gitbutlerapp/gitbutler · error · anyhow::Error

Worktree {name} has no usable HEAD

Error message

Worktree {name} has no usable HEAD

What it means

Thrown by active_worktree() in but-api when a named worktree exists and is not archived, but ctx.worktree_head() resolves to None, meaning HEAD cannot be turned into a usable commit. The source comment lists the accepted causes: an unborn branch, a checkout of the GitButler workspace ref, or a broken HEAD. The library refuses to continue because the caller needs a real commit to build a ChangeSource for an editor-backed operation.

Source

Thrown at crates/but-api/src/worktrees.rs:72

/// Look up the *active* linked worktree named `name`.
///
/// Every command here operates on active worktrees only - an archived one is
/// hidden from the graph, so operations against it could not be materialized.
///
/// Must not be called while a database handle is borrowed, see
/// [`but_ctx::Context::worktrees_with_state()`].
fn active_worktree(ctx: &but_ctx::Context, name: &str) -> Result<WorktreeEntry> {
    let worktree = ctx
        .worktrees_with_state()?
        .into_iter()
        .find(|worktree| worktree.name == name.as_bytes())
        .with_context(|| format!("Worktree {name} does not exist"))?;
    if worktree.archived {
        bail!("Worktree {name} is archived");
    }
    if ctx.worktree_head(worktree.name.as_bstr())?.is_none() {
        // Unborn, workspace-ref checkout, or broken - nothing to operate on.
        bail!("Worktree {name} has no usable HEAD");
    }
    Ok(worktree)
}

/// Open the checkout that `source` reads its changes from, returning its stable
/// name along with a plain from-disk open of it, or `None` for the main worktree.
///
/// Callers turn this into a [`ChangeSource`](but_workspace::commit::ChangeSource)
/// for the duration of an editor-backed operation.
///
/// Must not be called while a database handle is borrowed, see
/// [`but_ctx::Context::worktrees_with_state()`].
pub(crate) fn open_changes_source(
    ctx: &but_ctx::Context,
    source: &ChangesSource,
) -> Result<Option<(BString, gix::Repository)>> {
    let ChangesSource::Worktree(name) = source else {
        return Ok(None);

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Make the first commit on the worktree's branch, or re-checkout an existing branch, so HEAD points at a real commit
  2. If HEAD targets the GitButler workspace ref, switch that worktree to a normal branch before running the operation
  3. Inspect the worktree's HEAD (in .git/worktrees/<name>/HEAD) for corruption; repair with git symbolic-ref or git checkout
  4. Choose a different, initialized worktree for the operation

Example fix

# before: worktree on a brand-new branch, HEAD is unborn
git worktree add ../wt -b brand-new
git -C ../wt log   # fails: does not have any commits yet

# after: give HEAD a commit (or base the worktree on an existing branch)
git -C ../wt commit --allow-empty -m 'init'
# now active_worktree() finds a usable HEAD
Defensive patterns

Strategy: validation

Validate before calling

// Rust: resolve HEAD yourself before any editor-backed worktree operation
let wt = ctx.worktrees_with_state()?.into_iter()
    .find(|w| w.name == name.as_bytes())
    .with_context(|| format!("worktree {name} missing"))?
    .clone();
if !wt.archived && ctx.worktree_head(wt.name.as_bstr())?.is_none() {
    // unborn / workspace-ref / broken HEAD — skip or prompt, don't call the API
    return Ok(WorktreeReadiness::NeedsInit);
}

Try / catch

// treat as a user-facing readiness problem, not a crash
match active_worktree(&ctx, name) {
    Err(e) if e.to_string().contains("no usable HEAD") => prompt_init_first(&name),
    r => r?,
}

Prevention

When it happens

Trigger: Any but-api operation that funnels through active_worktree(ctx, name) (opening a change source / editor operation on a worktree) when the worktree's HEAD is unborn (fresh 'git worktree add -b new-branch' with no commits yet), points at the GitButler workspace ref instead of a branch, or its HEAD file/ref is corrupt so worktree_head() returns None.

Common situations: A just-created worktree on a brand-new branch (git prints 'branch is unborn'); GitButler-managed worktrees checked out to the workspace ref; a repo copied or synced with a damaged .git/HEAD or a detached-but-missing target ref.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/451250bb3b2610a7. Report an issue: GitHub.