gitbutlerapp/gitbutler · error
Ambiguous branch ' ', matches multiple items
Error message
Ambiguous branch '{branch_id}', matches multiple items What it means
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.
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.
Example fix
// before: ambiguous prefix but merge feature // error: Ambiguous branch 'feature', matches multiple items // after: exact branch name or full id but merge feature-2 // or but merge br#3
Defensive patterns
Strategy: validation
Validate before calling
// Require an exact, unambiguous branch name before invoking:
const branches = JSON.parse(runBut(['branch', 'list', '--json']).stdout);
const matches = branches.filter(b => b.name === branchId);
if (matches.length !== 1) {
throw new Error(`Use an exact branch name or full CLI ID; candidates: ${branches.map(b => b.id).join(', ')}`);
} Try / catch
try {
runBut(['merge', branchId]);
} catch (e) {
if (e.stderr.includes('matches multiple items')) {
// retry with a full branch id resolved from the listing
runBut(['merge', fullBranchIdFromListing]);
} else {
throw e;
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Could not find branch
- Expected a branch ID, got
- Ambiguous branch ' ', matches multiple items
- Ambiguous branch identifier
- `but land` requires an active GitButler workspace…
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/cd1b7baf89276017.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but/src/command/legacy/merge/mod.rs:48
let mut guard = ctx.exclusive_worktree_access();
{
let (_repo, ws, _db) = ctx.workspace_and_db_with_perm(guard.read_permission())?;
if !ws.kind.has_managed_ref() {
bail!(
"`but merge` requires an active GitButler workspace (`gitbutler/workspace`). \
Switch into the workspace and try again."
);
}
}
let id_map = IdMap::new_from_context(ctx, guard.read_permission())?;
let resolved_ids = id_map.parse_using_context(branch_id, ctx)?;
if resolved_ids.is_empty() {
bail!("Could not find branch: {branch_id}");
}
if resolved_ids.len() > 1 {
bail!("Ambiguous branch '{branch_id}', matches multiple items");
}
let branch_name = match &resolved_ids[0] {
CliId::Branch(branch) => branch.name.clone(),
other => bail!("Expected a branch ID, got {}", other.kind_for_humans()),
};
let base_branch =
but_api::legacy::virtual_branches::get_base_branch_data(ctx, guard.write_permission())?
.ok_or_else(|| anyhow::anyhow!("No base branch configured"))?;
(branch_name, base_branch)
};
// Display strings for the prompt and the final report. The API recomputes the target/remote
// configuration internally; the CLI only needs these names to describe what's about to happen.
let target_branch_name = base_branch.short_name.clone();
let push_remote_name = if base_branch.push_remote_name.is_empty() {
base_branch.remote_name.clone()View on GitHub (pinned to 58e5313667)