{"record":{"id":"abf980298668a20e","repo":"gitbutlerapp/gitbutler","slug":"can-only-check-out-local-branches-under-refs-heads-or-remote","errorCode":null,"errorMessage":"Can only check out local branches under refs/heads or remote-tracking branches under refs/remotes, got '{}'","messagePattern":"Can only check out local branches under refs/heads or remote-tracking branches under refs/remotes, got '(.+?)'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-api/src/branch.rs","lineNumber":1687,"sourceCode":"}\n\n/// Checks out a branch under caller-held exclusive repository access without\n/// creating an oplog entry.\n///\n/// See [`branch_checkout()`] for the accepted branch names.\npub fn branch_checkout_with_perm_only(\n    ctx: &mut but_ctx::Context,\n    reference_name: gix::refs::FullName,\n    perm: &mut RepoExclusive,\n) -> anyhow::Result<BranchCheckoutResult> {\n    {\n        let repo = ctx.repo.get()?;\n        let reference_name = match reference_name.category() {\n            Some(gix::refs::Category::LocalBranch) => reference_name,\n            Some(gix::refs::Category::RemoteBranch) => {\n                but_workspace::branch::local_tracking_branch(&repo, reference_name.as_ref())?\n            }\n            _ => bail!(\n                \"Can only check out local branches under refs/heads or remote-tracking branches under refs/remotes, got '{}'\",\n                reference_name.as_bstr()\n            ),\n        };\n        let current_head = repo\n            .head_id()\n            .context(\"Cannot check out a branch while HEAD is unborn\")?\n            .detach();\n        let mut reference = repo\n            .find_reference(reference_name.as_ref())\n            .with_context(|| format!(\"Could not find ref '{}'\", reference_name.as_bstr()))?;\n        let target = reference\n            .peel_to_id()\n            .with_context(|| format!(\"Could not resolve ref '{}'\", reference_name.as_bstr()))?\n            .detach();\n        let target_commit = repo.find_commit(target).with_context(|| {\n            format!(\n                \"Ref '{}' does not point to a commit\",","sourceCodeStart":1669,"sourceCodeEnd":1705,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-api/src/branch.rs#L1669-L1705","documentation":"`branch_checkout` only accepts local branches under `refs/heads` or remote-tracking branches under `refs/remotes` (the latter resolved to their local tracking branch via `local_tracking_branch`). Any other reference category — tags, detached names, custom namespaces — is rejected with the offending refname in the message.","triggerScenarios":"Calling the checkout API with a full refname whose `category()` is neither `LocalBranch` nor `RemoteBranch`, e.g. a tag (`refs/tags/v1.0`), a custom ref namespace, or an unqualified/invalid name that resolves to no known category.","commonSituations":"Trying to check out a tag through the branch-checkout endpoint; passing a raw object ID or symbolic ref; custom refs created by other tooling; client sending short names that fail categorization.","solutions":["Pass a full `refs/heads/<name>` local branch ref, or `refs/remotes/<remote>/<name>` to check out its tracking branch","To get a tag's content, create a branch at the tag and check out that branch instead","Normalize/qualify the refname before calling so `category()` can classify it"],"exampleFix":"// before\nawait api.checkoutBranch(\"refs/tags/v1.0\");\n// after\nawait api.createBranch({ name: \"release-v1.0\", startpoint: \"refs/tags/v1.0\" });\nawait api.checkoutBranch(\"refs/heads/release-v1.0\");","handlingStrategy":"validation","validationCode":"function isCheckoutableRef(ref: string): boolean {\n  return ref.startsWith(\"refs/heads/\") || ref.startsWith(\"refs/remotes/\");\n}\nif (!isCheckoutableRef(ref)) throw new Error(`cannot checkout ref: ${ref}`);","typeGuard":"function isCheckoutableRef(ref: string): ref is `refs/heads/${string}` | `refs/remotes/${string}` {\n  return ref.startsWith(\"refs/heads/\") || ref.startsWith(\"refs/remotes/\");\n}","tryCatchPattern":"try {\n  await api.checkoutBranch(ref);\n} catch (e) {\n  if (String(e).includes(\"Can only check out local branches\")) {\n    console.warn(\"checkout requires refs/heads/* or refs/remotes/*; create a branch at the tag instead\");\n  } else { throw e; }\n}","preventionTips":["Qualify ref names (refs/heads/..., refs/remotes/...) before checkout","Route tag checkouts through a temporary branch","Use local_tracking_branch resolution for remotes on the client side","Validate ref category before sending checkout requests from scripts"],"tags":["git","branch","checkout","validation"],"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"}