gitbutlerapp/gitbutler · error

`but merge` requires an active GitButler workspace…

Error message

`but merge` requires an active GitButler workspace (`gitbutler/workspace`). Switch into the workspace and try again.

What it means

The `but merge` command only works inside an active GitButler workspace: the repository must have the managed `gitbutler/workspace` ref, and the loaded workspace kind must report `has_managed_ref()`. The CLI checks this up front (the API enforces it again before mutating anything) and bails with this message when the workspace is absent or unmanaged, because landing a branch onto a target is only defined for GitButler-managed workspaces.

Solutions

  1. Initialize or re-enter a GitButler workspace first (e.g. `but init` or `but` to enter the TUI and create a workspace), then run `but merge` again.
  2. Check `git rev-parse gitbutler/workspace` in the repo to confirm the managed ref exists; if missing, the workspace is not active.
  3. Confirm the working directory: run the command inside the GitButler-managed repository root, not a nested/plain checkout.
  4. If the workspace was expected to exist, inspect workspace state with `but workspace` / `but status` and re-create it if it was torn down.

Example fix

// before: fails in a plain git repo
but merge branch-abc
// error: `but merge` requires an active GitButler workspace
// after: ensure workspace exists first
but init
but merge branch-abc
Defensive patterns

Strategy: validation

Validate before calling

// Check for the managed workspace ref before invoking but merge:
const refExists = runGit(['rev-parse', '--verify', 'gitbutler/workspace']).success;
if (!refExists) {
  throw new Error('Not in a GitButler workspace; run but init first.');
}

Try / catch

try {
  runBut(['merge', branchId]);
} catch (e) {
  if (e.stderr.includes('requires an active GitButler workspace')) {
    runBut(['init']); // create/enter workspace, then retry once
    runBut(['merge', branchId]);
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Running `but merge <branch-id>` in a plain Git repository with no `gitbutler/workspace` ref, or in a repository where the workspace exists but its `kind` does not have a managed ref (e.g. after workspace teardown, or while in a detached/unmanaged state). The check runs at the top of `handle` after acquiring exclusive worktree access via `ctx.workspace_and_db_with_perm`.

Common situations: Developer runs `but merge` in a repo that has never had `but init`/workspace creation. The workspace was just removed (e.g. after landing the last branch or `but workspace goto` onto a plain branch). The command is executed with `-C <path>` pointing at a non-workspace sub-repo or wrong directory.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/91150bededb1162b. Report an issue: GitHub.

Appendix: source

Thrown at crates/but/src/command/legacy/merge/mod.rs:35

pub fn handle(
    ctx: &mut Context,
    out: &mut OutputChannel,
    branch_id: &str,
    yes: bool,
    no_ff: bool,
    whole_stack: bool,
) -> anyhow::Result<()> {
    // Resolve the branch identifier and read the target configuration. The managed-workspace guard
    // runs here for a friendly message before the prompt; the API enforces it again, along with the
    // bottom-segment, conflicted-commit, and triangular-remote guards, before mutating anything.
    let (branch_name, base_branch) = {
        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()),

View on GitHub (pinned to 58e5313667)