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 existsView on GitHub (pinned to caf1f223d3)
Solutions
- 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.
- Change cwd to the main worktree before discovery, or pass an explicit main-worktree path to the CLI.
- 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
- Discover repositories from a configured main-worktree path, not from cwd, in tools that touch worktree state.
- In CLIs run from linked worktrees, resolve `common_dir()` and re-open before DB-backed queries.
- Unit-test this guard with a `git worktree add` fixture.
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
- Invalid token format
- Login token expired. Please log in to GitButler again.
- HTTP Error ${response.statusText}: ${text}
- worktree-changes are always set if there are hunks
- Worktree {name} has no usable HEAD
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/65d89944613be169.
Report an issue: GitHub.