{"record":{"id":"b6bfa74d4b9f934c","repo":"gitbutlerapp/gitbutler","slug":"worktree-name-has-a-workspace-ref-checked-out-s","errorCode":null,"errorMessage":"Worktree {name} has a workspace ref checked out, so there is no branch to commit to","messagePattern":"Worktree (.+?) has a workspace ref checked out, so there is no branch to commit to","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but/src/utils/worktrees.rs","lineNumber":19,"sourceCode":"use anyhow::Context as _;\nuse bstr::BStr;\n\n/// The branch checked out in the linked worktree `name`, which is where a commit made from that\n/// checkout goes and where a commit moved onto its lane lands.\n///\n/// A detached worktree has no branch to move, so it is refused rather than silently targeting\n/// somewhere else. The same goes for a workspace ref: such worktrees never render a heading,\n/// but the checkout can change between render and confirm, and a workspace ref must never\n/// receive a commit meant for a branch.\npub(crate) fn worktree_branch(\n    repo: &gix::Repository,\n    name: &BStr,\n) -> anyhow::Result<gix::refs::FullName> {\n    let worktree_repo = but_workspace::worktrees::open_worktree_repo(repo, name)?;\n    let branch = worktree_repo.head_name()?.with_context(|| {\n        format!(\"Worktree {name} has a detached HEAD, so there is no branch to commit to\")\n    })?;\n    anyhow::ensure!(\n        !but_core::is_workspace_ref_name(branch.as_ref()),\n        \"Worktree {name} has a workspace ref checked out, so there is no branch to commit to\"\n    );\n    Ok(branch)\n}\n","sourceCodeStart":1,"sourceCodeEnd":25,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but/src/utils/worktrees.rs#L1-L25","documentation":"worktree_branch() resolves which branch a worktree's HEAD is on before an operation may commit to it. Besides detached HEAD, it explicitly refuses GitButler workspace refs (but_core::is_workspace_ref_name): such worktrees render without a heading but their HEAD points at internal workspace state, and a commit meant for a branch must never land on the workspace ref. The checkout can change between TUI render and confirm, so this is re-checked at confirm time.","triggerScenarios":"Confirming an operation that targets a worktree (commit, branch move) after that worktree's HEAD was switched to a GitButler workspace ref, by a re-render in the TUI or by another but/GitButler process touching the worktree concurrently.","commonSituations":"Race between TUI render and confirmation; running the but CLI/TUI and the GitButler desktop app against the same repository at the same time.","solutions":["Refresh/re-render the TUI, reselect the target, and confirm again","In the affected worktree, put HEAD back on a real branch: `git switch <branch>`","Run only one GitButler surface (app or CLI) per repository at a time"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"let head = worktree_repo.head_name()?;\nmatch head {\n    Some(name) if !but_core::is_workspace_ref_name(name.as_ref()) => Ok(name),\n    Some(_) => anyhow::bail!(\"workspace ref checked out; reselect after refresh\"),\n    None => anyhow::bail!(\"detached HEAD; no branch\"),\n}","typeGuard":null,"tryCatchPattern":"On this error, re-read the worktree's HEAD and restart from a fresh render; never retry the commit against the workspace-ref state.","preventionTips":["Re-validate HEAD at confirm time, not only at render time","One writer per repository: avoid concurrent app and CLI mutations"],"tags":["worktree","workspace-ref","commit","race-condition","head"],"backgroundTag":"no-branch-checked-out","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}