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 writeView on GitHub (pinned to caf1f223d3)
Solutions
- Use a top-level directory starting with 'gitbutler', e.g. 'gitbutler' or 'gitbutler-work'
- Point the storage path outside .git via the non-in-git override if a different location is really needed
- 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
- Offer a preset list of valid storage paths in the UI instead of free-form input
- Beware case-insensitive filesystems: 'GitButler' matches, 'git-butler' does not
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
- configured storage path '{}' resolves to '.git' itself; choo
- Invalid message format
- HTTP Error ${response.statusText}: ${text}
- Invalid section for global key: {key}
- Cannot move branch {subject_branch_name} onto itself
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/985222843a9ef763.
Report an issue: GitHub.