{"record":{"id":"e72aac67e4780177","repo":"gitbutlerapp/gitbutler","slug":"expected-a-local-branch-but-is-not-under-refs-heads","errorCode":null,"errorMessage":"Expected a local branch, but '{}' is not under refs/heads/","messagePattern":"Expected a local branch, but '(.+?)' is not under refs/heads/","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but/src/command/branch/update.rs","lineNumber":149,"sourceCode":"    }\n}\n\nfn resolve_local_branch(ctx: &but_ctx::Context, branch: &str) -> anyhow::Result<FullName> {\n    let candidate = normalize_local_branch_ref(branch)?;\n    let repo = ctx.repo.get()?;\n    let Some(reference) = repo.try_find_reference(candidate.as_ref())? else {\n        bail!(\"Local branch '{}' not found\", candidate.shorten());\n    };\n    let reference = reference.detach();\n    match reference.name.category() {\n        Some(Category::LocalBranch) => Ok(reference.name),\n        Some(Category::RemoteBranch) => {\n            bail!(\n                \"Expected a local branch, but '{}' is a remote-tracking branch\",\n                reference.name.shorten()\n            )\n        }\n        _ => bail!(\n            \"Expected a local branch, but '{}' is not under refs/heads/\",\n            reference.name.shorten()\n        ),\n    }\n}\n\nfn normalize_local_branch_ref(branch: &str) -> anyhow::Result<FullName> {\n    let full = if branch.starts_with(\"refs/heads/\") {\n        branch.to_owned()\n    } else if branch.starts_with(\"refs/\") {\n        bail!(\"Only local branches under refs/heads/ are supported\");\n    } else {\n        format!(\"refs/heads/{branch}\")\n    };\n    FullName::try_from(full).map_err(|err| anyhow::anyhow!(\"Invalid branch name: {err}\"))\n}\n\nfn output_apply_result(","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but/src/command/branch/update.rs#L131-L167","documentation":"After finding the reference, resolve_local_branch verifies it is under refs/heads/. Any reference that is neither a local nor remote branch (e.g. a tag, stash ref, or notes ref) is rejected because `but branch update` supports only local branches.","triggerScenarios":"Passing something like a tag name (`v1.0`) or other non-branch ref to `but branch update`; try_find_reference matches the tag, whose category is not LocalBranch or RemoteBranch, hitting the fallthrough bail.","commonSituations":"Confusing tags with branches; passing full refs of other categories (refs/stash, refs/notes/*) copied from tooling output.","solutions":["Pass a local branch name that lives under refs/heads/ (check with `git branch --list <name>`).","If you meant to operate on a tag, use the appropriate git/tag command instead of `but branch update`.","Create a local branch if the target should be one: `git switch -c <name>`."],"exampleFix":"// before\nbut branch update v1.0 --to abc123\n// after\nbut branch update release-1.0 --to abc123","handlingStrategy":"validation","validationCode":"const isBranch = execSync(`git show-ref --verify --quiet refs/heads/${name} && echo yes || echo no`).toString().trim() === 'yes';\nif (!isBranch) throw new Error(`'${name}' is not a local branch under refs/heads/`);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Verify the target is a branch, not a tag, with `git show-ref refs/heads/<name>`.","Don't pass tags or stash/notes refs to branch commands.","Sanitize any ref input against Category::LocalBranch expectations."],"tags":["git","branch","ref-category","cli"],"backgroundTag":"invalid-argument-value","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"}