gitbutlerapp/gitbutler · error

non-main worktrees are not supported

Error message

non-main worktrees are not supported

What it means

AddProjectOutcome::NonMainWorktree mapped through try_project: the path is a git linked worktree (created with `git worktree add`), not the repository's main worktree. GitButler manages the main worktree (it writes refs and workspace state under the repo's primary .git dir) and rejects secondary worktrees.

Source

Thrown at crates/gitbutler-project/src/project.rs:327

            AddProjectOutcome::Added(p) => p,
            _ => panic!("called `AddProjectOutcome::unwrap_project()` on a non-project outcome"),
        }
    }

    /// Try to get the `Project`, returning an error if it was not added.
    pub fn try_project(self) -> anyhow::Result<Project> {
        match self {
            AddProjectOutcome::Added(p) => Ok(p),
            AddProjectOutcome::AlreadyExists(_) => Err(anyhow::anyhow!("project already exists")),
            AddProjectOutcome::PathNotFound => Err(anyhow::anyhow!("project path not found")),
            AddProjectOutcome::NotADirectory => {
                Err(anyhow::anyhow!("project path is not a directory"))
            }
            AddProjectOutcome::BareRepository => {
                Err(anyhow::anyhow!("bare repositories are not supported"))
            }
            AddProjectOutcome::NonMainWorktree => {
                Err(anyhow::anyhow!("non-main worktrees are not supported"))
            }
            AddProjectOutcome::NoWorkdir => Err(anyhow::anyhow!("no workdir found for repository")),
            AddProjectOutcome::NoDotGitDirectory => {
                Err(anyhow::anyhow!("no .git directory found in repository"))
            }
            AddProjectOutcome::ReftableRefFormatUnsupported => Err(anyhow::anyhow!(
                "unsupported repository reference format: reftable"
            )),
            AddProjectOutcome::NotAGitRepository(msg) => {
                Err(anyhow::anyhow!("not a git repository: {msg}"))
            }
        }
    }
}

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Add the main worktree instead — resolve the main path with `git worktree list` and pass that directory
  2. If you need an isolated checkout, clone the repository rather than using a linked worktree
  3. Pre-detect: a `.git` regular file (not directory) containing `gitdir:` indicates a linked worktree

Example fix

// before
let project = add_project(&path, ...).try_project()?; // linked worktree -> rejected

// after: resolve to the main worktree first
fn main_worktree(path: &Path) -> Option<PathBuf> {
    let out = std::process::Command::new("git")
        .args(["-C", path.to_str()?, "worktree", "list", "--porcelain"])
        .output().ok()?;
    let line = String::from_utf8(out.stdout).ok()?.lines().next()?;
    line.strip_prefix("worktree ").map(PathBuf::from)
}
let path = main_worktree(&path).unwrap_or(path);
let project = add_project(&path, ...).try_project()?;
Defensive patterns

Strategy: validation

Validate before calling

// a linked worktree has a .git *file* pointing at the main repo's worktrees dir
let dot = path.join(".git");
let is_linked_worktree = dot.is_file()
    && std::fs::read_to_string(&dot).map(|c| c.starts_with("gitdir:")).unwrap_or(false);
if is_linked_worktree {
    // resolve the main worktree: git -C <path> worktree list --porcelain | head -1
    anyhow::bail!("linked worktree — add the main worktree instead");
}

Type guard

fn is_main_worktree(path: &Path) -> bool {
    let dot = path.join(".git");
    dot.is_dir() // main worktrees have a .git directory; linked ones have a gitdir: file
}

Try / catch

match add_project(&path, ...) {
    AddProjectOutcome::NonMainWorktree => offer_to_add_main_worktree(&path), // resolve via `git worktree list`
    outcome => outcome.try_project(),
}

Prevention

When it happens

Trigger: add_project on a directory whose .git is a file pointing at <main>/.git/worktrees/<name>; multiple `git worktree add` checkouts of the same repo; adding the same repository twice through two of its worktrees.

Common situations: Developers using linked worktrees for parallel branches trying to onboard each one; automation that picks an existing checkout which happens to be a linked worktree.

Related errors


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