gitbutlerapp/gitbutler · error

{}

Error message

{}

What it means

This helper shells out to `git worktree <subcommand>` and, when git exits non-zero, surfaces git's captured stderr verbatim as the error. It is a transparent pass-through of any `git worktree add/remove/...` failure, wrapped only by the 'Failed to run `git worktree <subcommand>`' context on spawn failure.

Solutions

  1. Read the message body — it is git's own stderr — and fix the underlying git issue it names
  2. For 'already used by worktree', remove/prune the other worktree or use a different branch (or `git worktree add --detach`)
  3. For removal failures, `git worktree unlock <path>` first or force removal
  4. Verify git is installed and on PATH if the failure is the spawn-context variant

Example fix

// handle git's stderr surface explicitly
match add(&repo, &path, branch, base) {
    Ok(short) => ...,
    Err(e) if e.to_string().contains("is already used by worktree") => {
        // pick another branch or detach
    }
    Err(e) => return Err(e),
}
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check branch availability
let registered = repo.worktrees()?.map(|w| w.ok()).collect::<Vec<_>>();
anyhow::ensure!(!registered.iter().any(|w| w.branch() == Ok(branch)), "branch already checked out elsewhere");

Try / catch

match add(&repo, &path, branch, base) {
    Err(e) if e.to_string().contains("is already used by worktree") => use_detached_or_other_branch(),
    Err(e) if e.to_string().contains("Failed to run `git worktree`") => check_git_installation(),
    r => r,
}

Prevention

When it happens

Trigger: Any underlying git failure from the add or remove wrappers: branch already checked out in another worktree, path exists or is not writable, worktree not registered, dirty/locked worktree on remove, invalid ref, or a missing/broken git binary.

Common situations: Adding a worktree for a branch that is already checked out elsewhere ('fatal: ... is already used by worktree ...'); removing a worktree with a lock file; run from an environment where git is not on PATH (the context message 'Failed to run...' variant); paths with characters git rejects.

Understand the failure class

Background: "git command failed": what it means when a tool shells out to git and git exits non-zero — this error's family across 21 libraries.

Related errors


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

Appendix: source

Thrown at crates/but-workspace/src/worktrees.rs:148

        .and_then(|worktree| worktree.id().map(ToOwned::to_owned))
        .context("git registered the new checkout as a linked worktree")
}

fn git_worktree(repo: &gix::Repository, subcommand: &str, args: &[&OsStr]) -> anyhow::Result<()> {
    let mut cmd = std::process::Command::new(gix::path::env::exe_invocation());
    // These would override `-C`.
    for var in ["GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE"] {
        cmd.env_remove(var);
    }
    let output = cmd
        .arg("-C")
        .arg(repo.workdir().unwrap_or(repo.common_dir()))
        .args(["worktree", subcommand])
        .args(args)
        .output()
        .with_context(|| format!("Failed to run `git worktree {subcommand}`"))?;
    if !output.status.success() {
        anyhow::bail!("{}", String::from_utf8_lossy(&output.stderr).trim());
    }
    Ok(())
}

/// The outcome of [`move_uncommitted_changes()`].
#[derive(Debug, Clone, Copy)]
pub struct MoveUncommittedChangesOutcome {
    /// Conflict markers were written into `main_repo`'s working directory and index.
    pub conflict_occurred: bool,
}

/// Move some or all of the uncommitted changes of the linked worktree `worktree_repo` into the
/// uncommitted changes of `main_repo`.
///
/// `worktree_repo` must share `main_repo`'s object database and have no object memory, as
/// returned by [`open_worktree_repo()`] - the same requirement `ChangeSource::Worktree`
/// (`crate::commit`) has, since this writes loose objects through it that `main_repo` must see
/// immediately.

View on GitHub (pinned to 58e5313667)