{"record":{"id":"99ff5422022c6b13","repo":"gitbutlerapp/gitbutler","slug":"head-points-to-the-gitbutler-workspace-branch","errorCode":null,"errorMessage":"HEAD points to the GitButler workspace branch","messagePattern":"HEAD points to the GitButler workspace branch","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/but-agentlog/src/environment.rs","lineNumber":392,"sourceCode":"                observed_targets: fallback,\n                error_kind: Some(SnapshotErrorKind::Workspace),\n            }),\n            _ => Err(error),\n        },\n    }\n}\n\nfn current_branch_targets(repo: &gix::Repository) -> anyhow::Result<ObservedTargets> {\n    let head = repo.head()?;\n    let Some(ref_name) = head.referent_name() else {\n        anyhow::bail!(\"HEAD is detached\");\n    };\n    let full_ref_name = ref_name.to_string();\n    if !full_ref_name.starts_with(\"refs/heads/\") {\n        anyhow::bail!(\"HEAD does not point to a local branch\");\n    }\n    if is_workspace_ref_name(ref_name) {\n        anyhow::bail!(\"HEAD points to the GitButler workspace branch\");\n    }\n    let branch = BranchTarget {\n        key: format!(\"ref:{full_ref_name}\"),\n        name: ref_name.shorten().to_string(),\n    };\n    // Derive the PR association from the forge review cache rather than stored\n    // metadata. Synthesize a metadata entry carrying the resolved number so a\n    // branch with a cached PR is reported even without stored branch metadata.\n    let reviews = match resolve_branch_pr(repo, ref_name, &forge_prs_by_head(repo)) {\n        Some(pull_request) => {\n            let mut metadata = Branch::default();\n            metadata.review.pull_request = Some(pull_request);\n            review_targets(&branch.key, Some(&metadata))\n        }\n        None => Vec::new(),\n    };\n    let mut observed_targets = ObservedTargets {\n        branches: vec![branch],","sourceCodeStart":374,"sourceCodeEnd":410,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-agentlog/src/environment.rs#L374-L410","documentation":"current_branch_targets resolves the repository's HEAD to a local branch target for the agentlog environment snapshot. GitButler manages a special 'workspace' ref that holds virtual-branch state; HEAD on that ref is not a real local branch the caller can report, so the function bails with this error. It is an intentional guard: the caller (workspace_snapshot_with_current_branch_fallback) uses the bail to decide the repo is in GitButler-managed mode rather than on a plain branch.","triggerScenarios":"Calling workspace_snapshot_with_current_branch_fallback (via current_branch_targets) on a repository where HEAD points to refs/heads/<gitbutler workspace branch> (is_workspace_ref_name returns true) — i.e. the GitButler workspace/apply flow has checked out its managed ref.","commonSituations":"Developer has GitButler installed and its workspace is active in the repo; running agentlog snapshotting while GitButler holds HEAD; mixing plain-git tooling with GitButler-managed repos; GitButler left the workspace ref checked out after an interrupted session.","solutions":["Detach from or switch to a normal branch (git switch <feature-branch>) so HEAD no longer points at the GitButler workspace ref","Deactivate GitButler's workspace management for the repo (quit/unapply in the GitButler UI or `but` CLI) before snapshotting","Treat this error in the caller as an expected signal that the repo is GitButler-managed and skip plain-branch snapshotting rather than surfacing it as a failure","Check `git symbolic-ref HEAD` first and only run current_branch_targets when the ref is not the GitButler workspace ref"],"exampleFix":"// before\nlet targets = workspace_snapshot_with_current_branch_fallback(&repo)?;\n// after\nmatch workspace_snapshot_with_current_branch_fallback(&repo) {\n    Ok(t) => t,\n    Err(e) if e.to_string().contains(\"GitButler workspace branch\") => {\n        // repo is GitButler-managed; skip plain-branch snapshot\n        ObservedTargets::default()\n    }\n    Err(e) => return Err(e),\n}","handlingStrategy":"fallback","validationCode":"let head_ref = repo.head()?.referent_name().map(|r| r.to_string());\nlet is_gitbutler_workspace = head_ref\n    .as_deref()\n    .map(is_workspace_ref_name)\n    .unwrap_or(false);\nif is_gitbutler_workspace { /* skip plain-branch snapshot path */ }","typeGuard":"fn is_plain_local_branch(repo: &gix::Repository) -> Option<String> {\n    let name = repo.head().ok()?.referent_name()?.to_string();\n    (name.starts_with(\"refs/headers/\") || name.starts_with(\"refs/heads/\"))\n        .then_some(name)\n        .filter(|n| !is_workspace_ref_name_short(n))\n}","tryCatchPattern":"match current_branch_targets(repo) {\n    Ok(targets) => targets,\n    Err(e) if e.to_string().contains(\"GitButler workspace branch\") => {\n        // expected: GitButler-managed repo; use workspace snapshot path\n        ObservedTargets::default()\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Check HEAD's symbolic ref before resolving branch targets and skip GitButler workspace refs","Handle this bail as a signal to use the GitButler workspace snapshot path instead of treating it as a failure","Test snapshotting in repos where GitButler is active to catch this early","Keep GitButler workspace state deactivated in CI fixtures unless explicitly testing it"],"tags":["git","gitbutler","head","workspace-branch"],"backgroundTag":"unsupported-operation","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}