gitbutlerapp/gitbutler · error

Expected a branch ID, got {}

Error message

Expected a branch ID, got {}

What it means

The branch ID given to `but land` resolved uniquely — but to the wrong kind of entity, reported via `kind_for_humans()` (a commit or an uncommitted file instead of a `CliId::Branch`). Land operates on lanes/branches only, so the handler bails naming what it actually got.

Source

Thrown at crates/but/src/command/legacy/land/mod.rs:53

                bail!(
                    "`but land` 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()
    } else {
        base_branch.push_remote_name.clone()
    };
    let target_display = format!("{push_remote_name}/{target_branch_name}");

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Use the lane/branch ID shown in the branches section of `but status`.
  2. Pass the plain branch name instead of an ID.
  3. If you meant to act on a commit, use a commit-oriented command (e.g. `but resolve <commit>`), not `but land`.

Example fix

# before
but land 4z --yes   # 4z is a commit ID -> Expected a branch ID, got commit

# after
but land b2 --yes   # b2 is the lane that contains commit 4z
Defensive patterns

Strategy: type-guard

Validate before calling

# Use lane IDs/names only; verify the ID appears in the branches section of `but status`
but status | grep -F '<id>'

Type guard

// Narrow before acting: land accepts only branches
match &resolved_ids[0] {
    CliId::Branch(branch) => { /* safe to land `branch.name` */ }
    other => { /* reject: expected a branch, got `other.kind_for_humans()` */ }
}

Prevention

When it happens

Trigger: `but land <commit-id>` or `but land <uncommitted-file-id>` — IDs that resolve fine, just not to a branch (land/mod.rs:52-54).

Common situations: Grabbing a commit's ID from `but status` output instead of its lane's; muscle memory from commands that do accept commits (e.g. `but resolve <commit>`).

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/70763d7abe669d60. Report an issue: GitHub.