gitbutlerapp/gitbutler · error · anyhow::Error

worktree state must be read from the main worktree - a linke

Error message

worktree state must be read from the main worktree - a linked-worktree context has its own database, letting adoption and archived state diverge

What it means

`worktrees_with_state()` refuses repositories where `git_dir() != common_dir()` — i.e. a repository opened inside a linked worktree, whose database (adoption/archived state) lives in the worktree's private git dir. Reading state there would silently diverge from the main worktree's database, so the function bails instead. The check uses the commondir redirect, which also catches worktrees of bare repositories that path heuristics miss.

Source

Thrown at crates/but-db/src/worktrees.rs:120

/// archived until explicitly unarchived.
///
/// Worktrees whose checkout is gone from disk (prunable) are never returned.
/// Entries are identity only - whether a worktree has a usable `HEAD` (readable,
/// born, not the workspace ref) is resolved freshly by [`worktree_head()`]
/// wherever a consumer actually needs it.
///
/// Errors when `repo` is itself a linked worktree: such a repository stores its
/// database in the worktree's private git dir, so adoption and archived state
/// would silently diverge from the main worktree's database.
pub fn worktrees_with_state(
    repo: &gix::Repository,
    db: &mut DbHandle,
) -> Result<Vec<WorktreeEntry>> {
    // The `commondir` redirect only exists in linked-worktree git dirs; unlike
    // `Kind::LinkedWorkTree`, which is a path heuristic requiring a literal
    // `.git` component, this also catches worktrees of bare repositories.
    if repo.git_dir() != repo.common_dir() {
        anyhow::bail!(
            "worktree state must be read from the main worktree - \
             a linked-worktree context has its own database, letting adoption \
             and archived state diverge"
        );
    }
    let (all_names, mut worktrees) = enumerate_worktrees(repo)?;

    let archived = adopt_and_read_archived(db, &all_names)?;

    for wt in &mut worktrees {
        wt.archived = archived.contains(&wt.name);
    }
    Ok(worktrees)
}

/// Enumerate the linked worktrees of `repo`, returning the names of ALL of them
/// (for adoption - a worktree that is unusable today must still be adopted today,
/// not when it becomes usable) along with the entries whose checkout still exists

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Open the repository from the main worktree (or the bare repo) instead: resolve `repo.common_dir()` and re-open a repository rooted there before calling.
  2. Change cwd to the main worktree before discovery, or pass an explicit main-worktree path to the CLI.
  3. Guard callers: compare `repo.git_dir()` and `repo.common_dir()` and route to the main worktree before invoking this API.

Example fix

// before
let entries = but_db::worktrees::worktrees_with_state(&repo, &mut db)?; // repo from linked worktree

// after
let main_git_dir = repo.common_dir().to_path_buf();
let main_repo = gix::open(main_git_dir)?;
let entries = but_db::worktrees::worktrees_with_state(&main_repo, &mut db)?;
Defensive patterns

Strategy: validation

Validate before calling

// Rust — ensure the repo handle is the main worktree before reading worktree state
fn main_worktree_repo(repo: &gix::Repository) -> anyhow::Result<gix::Repository> {
    if repo.git_dir() == repo.common_dir() {
        Ok(repo.clone_shallow()) // already main
    } else {
        gix::open(repo.common_dir()).map_err(Into::into)
    }
}

Type guard

fn is_main_worktree(repo: &gix::Repository) -> bool {
    repo.git_dir() == repo.common_dir()
}

Try / catch

let repo = main_worktree_repo(&repo)?; // normalize before the call
let entries = but_db::worktrees::worktrees_with_state(&repo, &mut db)?;

Prevention

When it happens

Trigger: Passing a `gix::Repository` discovered from inside a linked worktree (`git worktree add`-created) to `worktrees_with_state()` instead of one opened on the main worktree.

Common situations: CLI run from a linked-worktree directory; tools that discover 'the repo' from cwd and land in `.../wt-1/.git` file pointing at the private git dir; tests using worktree fixtures.

Related errors


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