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
- Read the message body — it is git's own stderr — and fix the underlying git issue it names
- For 'already used by worktree', remove/prune the other worktree or use a different branch (or `git worktree add --detach`)
- For removal failures, `git worktree unlock <path>` first or force removal
- 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
- Read the message directly — it is git's own stderr and names the real cause
- Ensure git is installed and on PATH in CI/agent environments
- Unlock or force-remove worktrees before scripted removal
- Avoid checking the same branch out in two worktrees
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
- `but setup` cannot run from a linked worktree; run it from…
- Could not create repository bundle
- Repository installers require the main worktree
- The repository at is a non-main worktree. GitButler…
- Aborting due to empty branch name
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)