{"record":{"id":"cd1b7baf89276017","repo":"gitbutlerapp/gitbutler","slug":"ambiguous-branch-branch-id-matches-multiple-items","errorCode":null,"errorMessage":"Ambiguous branch '{branch_id}', matches multiple items","messagePattern":"Ambiguous branch '(.+?)', matches multiple items","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but/src/command/legacy/merge/mod.rs","lineNumber":48,"sourceCode":"        let mut guard = ctx.exclusive_worktree_access();\n\n        {\n            let (_repo, ws, _db) = ctx.workspace_and_db_with_perm(guard.read_permission())?;\n            if !ws.kind.has_managed_ref() {\n                bail!(\n                    \"`but merge` requires an active GitButler workspace (`gitbutler/workspace`). \\\n                     Switch into the workspace and try again.\"\n                );\n            }\n        }\n\n        let id_map = IdMap::new_from_context(ctx, guard.read_permission())?;\n        let resolved_ids = id_map.parse_using_context(branch_id, ctx)?;\n        if resolved_ids.is_empty() {\n            bail!(\"Could not find branch: {branch_id}\");\n        }\n        if resolved_ids.len() > 1 {\n            bail!(\"Ambiguous branch '{branch_id}', matches multiple items\");\n        }\n\n        let branch_name = match &resolved_ids[0] {\n            CliId::Branch(branch) => branch.name.clone(),\n            other => bail!(\"Expected a branch ID, got {}\", other.kind_for_humans()),\n        };\n\n        let base_branch =\n            but_api::legacy::virtual_branches::get_base_branch_data(ctx, guard.write_permission())?\n                .ok_or_else(|| anyhow::anyhow!(\"No base branch configured\"))?;\n        (branch_name, base_branch)\n    };\n\n    // Display strings for the prompt and the final report. The API recomputes the target/remote\n    // configuration internally; the CLI only needs these names to describe what's about to happen.\n    let target_branch_name = base_branch.short_name.clone();\n    let push_remote_name = if base_branch.push_remote_name.is_empty() {\n        base_branch.remote_name.clone()","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but/src/command/legacy/merge/mod.rs#L30-L66","documentation":"When the branch identifier passed to `but merge` resolves through the `IdMap` to more than one item, the command cannot pick a target safely and bails with this message. `parse_using_context` performs fuzzy/prefix-style matching over workspace items (branches, commits, files, etc.), so a short or generic identifier can match several entries; the CLI refuses to guess. The input is ambiguous, not missing.","triggerScenarios":"Calling `but merge <branch_id>` where `id_map.parse_using_context` returns `resolved_ids.len() > 1`, e.g. a short prefix that matches multiple branch IDs, a branch name that collides with another item type or another branch, or a fragment matched by several workspace segments.","commonSituations":"Typing a shortened ID prefix like `br` or `b1` that matches more than one item. Two branches with similar names (e.g. `feature` and `feature-2`) where a partial name matches both. Using a commit/file ID in a context where it also matches a branch. Large workspaces where short positional IDs wrap around.","solutions":["Re-run with a longer, more specific identifier: the full CLI ID (e.g. `br#3`) or the exact branch name.","Run `but branch list` to see all matching items and disambiguate by picking the exact ID.","Use a full unique branch name instead of a prefix or fragment.","If two branches genuinely share a name, rename one (`but branch rename`) so identifiers are unique."],"exampleFix":"// before: ambiguous prefix\nbut merge feature\n// error: Ambiguous branch 'feature', matches multiple items\n// after: exact branch name or full id\nbut merge feature-2\n// or\nbut merge br#3","handlingStrategy":"validation","validationCode":"// Require an exact, unambiguous branch name before invoking:\nconst branches = JSON.parse(runBut(['branch', 'list', '--json']).stdout);\nconst matches = branches.filter(b => b.name === branchId);\nif (matches.length !== 1) {\n  throw new Error(`Use an exact branch name or full CLI ID; candidates: ${branches.map(b => b.id).join(', ')}`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  runBut(['merge', branchId]);\n} catch (e) {\n  if (e.stderr.includes('matches multiple items')) {\n    // retry with a full branch id resolved from the listing\n    runBut(['merge', fullBranchIdFromListing]);\n  } else {\n    throw e;\n  }\n}","preventionTips":["Pass full branch CLI IDs (br#3) or exact full branch names, not prefixes or fragments.","List branches (but branch list) to disambiguate when names are similar.","Rename duplicate-sounding branches so identifiers stay unique.","Avoid passing commit/file IDs to but merge; it only accepts branch identifiers."],"tags":["cli","gitbutler","branch-resolution","ambiguity"],"backgroundTag":"ambiguous-identifier","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"}