gitbutlerapp/gitbutler · error
Cannot unapply a branch from an ad-hoc detached workspace
Error message
Cannot unapply a branch from an ad-hoc detached workspace
What it means
unapply needs a workspace reference name to update workspace metadata, but ws.ref_name() returned None — this is a detached, unnamed (ad-hoc) workspace with no ref to hang metadata or ref updates on. Unapply fundamentally requires a named workspace to record its effects, so it refuses.
Source
Thrown at crates/but-workspace/src/branch/unapply.rs:252
let branch_in_ws = ws.find_segment_and_stack_by_refname(branch);
if branch_in_ws.is_none() {
if branch_ref.is_none() {
bail!(
"Cannot unapply non-existing branch '{branch}'",
branch = branch.shorten()
);
}
// The branch exists in Git, but does not in the workspace: Nothing to do.
return Ok(Outcome::new(Cow::Borrowed(workspace)));
}
let branch_stack_was_entrypoint = branch_in_ws
.is_some_and(|(stack, _)| stack.segments.iter().any(|segment| segment.is_entrypoint));
let workspace_tip_was_entrypoint = ws.is_entrypoint();
let Some(workspace_ref_name) = workspace_ref_name else {
// This is an ad-hoc workspace by merit of being unnamed.
bail!("Cannot unapply a branch from an ad-hoc detached workspace");
};
let mut ws_md = meta.workspace(workspace_ref_name.as_ref())?;
if ws.kind.has_managed_ref() || ws.has_metadata() {
ws.reconcile_metadata(&mut ws_md)?;
}
let branch_removed_from_ws_meta = ws_md.unapply_branch(branch);
if !branch_removed_from_ws_meta {
// The branch wasn't in workspace metadata, yet it was present, so also delete its branch metadata
// as it could be used to disambiguate the segment.
// TODO: this will actually be observable even if it doens't work, unless it's run in a transaction, which right now it's not!
// Should be able to redo the traversal with an overlay that hides branch metadata, but I'd say it's not important enough.
meta.remove(branch)?;
let graph = ws
.graph
.redo_traversal_with_overlay(repo, meta, Overlay::default())?;
let workspace = graph.into_workspace()?;
if workspace.refname_is_segment(branch) {
bail!(View on GitHub (pinned to caf1f223d3)
Solutions
- Put the workspace on a named reference first (create/re-checkout the workspace ref), then retry.
- If HEAD is detached, attach it to a branch before doing workspace operations.
- For workspaces you want gone, delete the workspace ref and metadata explicitly rather than unapplying branches.
Example fix
// before unapply(repo, meta, &ws, branch, opts)?; // after ensure!(ws.ref_name().is_some(), "unnamed/detached workspace: attach a workspace ref first"); unapply(repo, meta, &ws, branch, opts)?;
Defensive patterns
Strategy: validation
Validate before calling
if ws.ref_name().is_none() {
// detached/unnamed workspace: attach a workspace ref before unapply
return attach_workspace_ref_and_retry();
} Type guard
fn workspace_is_named(ws: &but_graph::Workspace) -> bool {
ws.ref_name().is_some()
} Try / catch
match unapply(repo, meta, &ws, branch, opts) {
Err(err) if err.to_string().contains("ad-hoc detached workspace") => {
Err(anyhow!("attach HEAD to a branch / create the workspace ref first"))
}
other => other,
} Prevention
- Never run workspace mutations while HEAD is detached.
- Recreate a deleted workspace ref (refs/heads/<workspace>) before unapplying.
- Check ws.ref_name() as a precondition in automation wrappers.
When it happens
Trigger: Calling unapply on a workspace whose kind is ad-hoc and which carries no reference name (anonymous entrypoint, e.g. HEAD detached or an unborn workspace ref).
Common situations: Repository opened while HEAD is detached; workspace ref (refs/heads/<workspace>) deleted outside GitButler; fresh repo with no commits where the workspace ref is unborn.
Related errors
- When using OpenRouter, you must provide a valid API key
- HTTP Error ${response.statusText}: ${text}
- Tearing off a branch requires a workspace common base
- Refusing to delete last named segment '{}' as it would leave
- Refusing to work on workspace whose workspace commit isn't a
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/cbed589739afb545.
Report an issue: GitHub.