{"record":{"id":"289911f89f215450","repo":"gitbutlerapp/gitbutler","slug":"can-only-delete-local-branches-under-refs-heads-g","errorCode":null,"errorMessage":"Can only delete local branches under refs/heads, got '{}'","messagePattern":"Can only delete local branches under refs/heads, got '(.+?)'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-api/src/branch.rs","lineNumber":1111,"sourceCode":"    #[but_api(crate::json::FullNameBytes)] ref_name: gix::refs::FullName,\n) -> anyhow::Result<BranchRemoveResult> {\n    let mut guard = ctx.exclusive_worktree_access();\n    branch_remove_with_perm(ctx, ref_name, guard.write_permission())\n}\n\n/// Remove the local branch `ref_name`, whether or not it is part of the current\n/// workspace projection, under caller-held exclusive repository access and\n/// record an oplog snapshot on success.\n///\n/// See [`branch_remove()`] for the checked-out-reference behaviour and\n/// [`but_workspace::branch::remove_reference()`] for the lower-level deletion.\npub fn branch_remove_with_perm(\n    ctx: &mut but_ctx::Context,\n    ref_name: gix::refs::FullName,\n    perm: &mut RepoExclusive,\n) -> anyhow::Result<BranchRemoveResult> {\n    if ref_name.category() != Some(gix::refs::Category::LocalBranch) {\n        bail!(\n            \"Can only delete local branches under refs/heads, got '{}'\",\n            ref_name.as_bstr()\n        );\n    }\n    if but_branches::is_technical_branch_name(ref_name.shorten()) {\n        bail!(\n            \"Cannot delete GitButler technical branch '{}'\",\n            ref_name.shorten()\n        );\n    }\n\n    let maybe_oplog_entry = but_oplog::UnmaterializedOplogSnapshot::from_details_with_perm(\n        ctx,\n        SnapshotDetails::new(OperationKind::DeleteBranch)\n            .with_trailers([Trailer::Name(ref_name.to_string())]),\n        perm.read_permission(),\n        DryRun::No,\n    );","sourceCodeStart":1093,"sourceCodeEnd":1129,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-api/src/branch.rs#L1093-L1129","documentation":"`branch_remove_with_perm` refuses to delete any reference whose category is not a local branch under `refs/heads`. GitButler branch deletion only operates on local branches; remote-tracking refs, tags, and other ref categories are rejected up front with the offending full refname in the message.","triggerScenarios":"Calling `branch_remove_with_perm` (or the SDK/server branch-delete endpoint) with a ref like `refs/remotes/origin/foo`, a tag, or another non-`refs/heads` reference.","commonSituations":"Passing a remote-tracking branch name to the delete API; a client resolving a branch to its full refname of the wrong category; scripting deletes against tags by mistake.","solutions":["Pass a fully-qualified local branch ref, e.g. `refs/heads/my-branch`","Strip remote prefixes and delete via the appropriate remote-deletion API instead","Verify `ref_name.category()` is `LocalBranch` before calling"],"exampleFix":"// before\nctx.branch_remove(\"refs/remotes/origin/feature\")?;\n// after\nctx.branch_remove(\"refs/heads/feature\")?; // local branch only","handlingStrategy":"validation","validationCode":"function isLocalBranch(ref: string): boolean {\n  return ref.startsWith(\"refs/heads/\");\n}\nif (!isLocalBranch(refName)) throw new Error(`refuse to delete non-local branch: ${refName}`);","typeGuard":"function isLocalBranchRef(ref: string): ref is `refs/heads/${string}` {\n  return ref.startsWith(\"refs/heads/\");\n}","tryCatchPattern":"try {\n  await api.branchRemove(ref);\n} catch (e) {\n  if (String(e).includes(\"Can only delete local branches under refs/heads\")) {\n    console.warn(`${ref} is not a local branch; use remote deletion API`);\n  } else { throw e; }\n}","preventionTips":["Always pass fully-qualified refs/heads/... names to delete APIs","Filter out remote-tracking refs and tags before bulk deletes","Resolve short branch names to refs/heads before calling","Keep client ref-resolution logic aligned with ref categories"],"tags":["git","branch","validation","refs"],"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"}