gitbutlerapp/gitbutler · error · anyhow::Error

configured storage path '{}' resolves inside '.git' but not

Error message

configured storage path '{}' resolves inside '.git' but not under a top-level 'gitbutler*' directory

What it means

The second in-git storage-path rule: the path must sit under a top-level `.git/<dir>` whose name starts with 'gitbutler' (case-insensitive). Anything else inside .git — 'info', 'objects', 'hooks', or arbitrary custom directories — could collide with git's own files and is rejected.

Source

Thrown at crates/but-project-handle/src/storage_path.rs:102

fn validate_in_git_storage_path(
    storage_path_relative_to_git_dir: &Path,
    storage_path: &Path,
) -> anyhow::Result<()> {
    let Some(Component::Normal(top_level_dir)) =
        storage_path_relative_to_git_dir.components().next()
    else {
        bail!(
            "configured storage path '{}' resolves to '.git' itself; choose a dedicated GitButler directory instead",
            storage_path.display()
        );
    };

    if !top_level_dir
        .to_string_lossy()
        .get(..DEFAULT_STORAGE_DIR_NAME.len())
        .is_some_and(|name| name.eq_ignore_ascii_case(DEFAULT_STORAGE_DIR_NAME))
    {
        bail!(
            "configured storage path '{}' resolves inside '.git' but not under a top-level 'gitbutler*' directory",
            storage_path.display()
        );
    }

    Ok(())
}

/// Name of the default GitButler storage directory inside the git dir.
pub const DEFAULT_STORAGE_DIR_NAME: &str = "gitbutler";

/// Git-dir-relative path of the refresh sentinel, `gitbutler/REFRESH`.
///
/// Single source of truth shared by the writer (`write_refresh_sentinel`) and
/// the watcher (`gitbutler_filemonitor`).
pub const REFRESH_SENTINEL_PATH: &str = "gitbutler/REFRESH";

/// Identity written into the refresh sentinel so a process can skip the write

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Use a top-level directory starting with 'gitbutler', e.g. 'gitbutler' or 'gitbutler-work'
  2. Point the storage path outside .git via the non-in-git override if a different location is really needed
  3. Remove the override to use the default .git/gitbutler

Example fix

# before
storage_path_relative_to_git_dir = "info/gitbutler"

# after
storage_path_relative_to_git_dir = "gitbutler-work"
Defensive patterns

Strategy: validation

Validate before calling

// Rust: require a top-level 'gitbutler*' directory
fn under_gitbutler_dir(rel: &std::path::Path) -> bool {
    matches!(rel.components().next(), Some(std::path::Component::Normal(c))
        if c.to_string_lossy().get(..9).is_some_and(|n| n.eq_ignore_ascii_case("gitbutler")))
}
assert!(under_gitbutler_dir(configured_rel), "storage must live under .git/gitbutler*");

Try / catch

Catch the bail from settings save and prompt with the two valid choices: a top-level 'gitbutler*' directory under .git, or a location outside .git.

Prevention

When it happens

Trigger: Configuring `storage_path_relative_to_git_dir` to a directory whose first component is not 'gitbutler*', e.g. 'info/gitbutler', 'my-storage', or a nested path under a non-gitbutler folder.

Common situations: Attempts to hide GitButler data inside git's own directories; typos like 'gitbutle' or 'git-butler'; copying configs between machines with different conventions.

Related errors


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