{"record":{"id":"989b7ecaa010248a","repo":"gitbutlerapp/gitbutler","slug":"expected-a-branch-id-got-mod","errorCode":null,"errorMessage":"Expected a branch ID, got {}","messagePattern":"Expected a branch ID, got (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but/src/command/legacy/merge/mod.rs","lineNumber":53,"sourceCode":"                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()\n    } else {\n        base_branch.push_remote_name.clone()\n    };\n    let target_display = format!(\"{push_remote_name}/{target_branch_name}\");\n","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but/src/command/legacy/merge/mod.rs#L35-L71","documentation":"`but merge` expects its argument to resolve to a branch (`CliId::Branch`). The match on `resolved_ids[0]` accepts only the `CliId::Branch` variant; any other resolved kind (commit, file, hunk, etc.) is rejected with this message, which includes the human-readable kind via `kind_for_humans()`. The identifier resolved successfully but points at a workspace item that cannot be landed.","triggerScenarios":"Calling `but merge <id>` with an identifier that resolves to exactly one non-branch item: a commit ID (e.g. from `but log`), a file or hunk ID, or a workspace/target reference. `parse_using_context` returns a single result, but it is not `CliId::Branch`, so the `other =>` bail arm fires.","commonSituations":"Copy-pasting a commit ID from `but log` output into `but merge` instead of a branch ID. Selecting a file ID from TUI output when intending to merge its branch. Confusion between `but merge` (lands branches) and other commands that accept commit IDs.","solutions":["Look up the branch containing the item with `but branch list` and pass its branch ID/name instead.","If you have a commit ID, find its parent branch (e.g. via `but log` or `but branch list`) and merge that branch.","Remember `but merge` lands whole branches onto the target; use the appropriate command (e.g. `but commit` operations) for commit-level actions.","Use the full branch CLI ID (`br#N`) copied from `but branch list` output to avoid resolving to other item kinds."],"exampleFix":"// before: commit id passed to merge\nbut merge cm#2\n// error: Expected a branch ID, got a commit\n// after: merge the branch that owns the commit\nbut branch list   # feature-x is br#1\nbut merge feature-x","handlingStrategy":"validation","validationCode":"// Ensure the identifier is a branch before calling but merge:\nconst branches = JSON.parse(runBut(['branch', 'list', '--json']).stdout);\nif (!branches.some(b => b.id === id || b.name === id)) {\n  throw new Error(`${id} is not a branch ID; but merge only accepts branch identifiers.`);\n}","typeGuard":"function isBranchId(id, branches) {\n  return branches.some(b => b.id === id || b.name === id);\n}","tryCatchPattern":"try {\n  runBut(['merge', id]);\n} catch (e) {\n  if (e.stderr.startsWith('Expected a branch ID, got')) {\n    // id resolved to a commit/file/hunk; map it to its owning branch and retry\n    runBut(['merge', owningBranchName]);\n  } else {\n    throw e;\n  }\n}","preventionTips":["Only pass IDs copied from branch listings (but branch list) to but merge.","Do not paste commit IDs (cm#N) from but log into but merge.","Remember but merge operates on whole branches; use commit-level commands for commits.","When automating, validate the id against the branch list before invoking."],"tags":["cli","gitbutler","wrong-argument-type","branch-resolution"],"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"}