gitbutlerapp/gitbutler · error
project path is not a directory
Error message
project path is not a directory
What it means
AddProjectOutcome::NotADirectory mapped through try_project: the given path exists but is a file (or special node), not a directory. add_project requires a directory because a repository worktree is a directory tree.
Source
Thrown at crates/gitbutler-project/src/project.rs:321
/// This is for tests only.
///
/// Unwraps the `Project` if the project was actually added.
/// Panics if it was not.
pub fn unwrap_project(self) -> Project {
match self {
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
- Check path.is_dir() before calling add_project
- If a symlink, resolve it (fs::canonicalize) and validate the target is a directory
- Point at the worktree root (the folder containing .git), never at a file inside it
- If the user supplied a URL, clone it first, then add the clone
Example fix
// before
let project = add_project(&path, ...).try_project()?; // file path -> 'not a directory'
// after
if !path.is_dir() {
anyhow::bail!("{} is not a directory — pass the repository worktree root", path.display());
}
let project = add_project(&path, ...).try_project()?; Defensive patterns
Strategy: validation
Validate before calling
let path = std::fs::canonicalize(&input)?;
if !path.is_dir() {
anyhow::bail!("{} is a file — pass the repository worktree root", path.display());
} Type guard
fn is_directory(p: impl AsRef<Path>) -> bool {
p.as_ref().is_dir() // follows symlinks; false for files
} Try / catch
match add_project(&path, ...) {
AddProjectOutcome::NotADirectory => reject_with_message("select the repository folder, not a file"),
outcome => outcome.try_project(),
} Prevention
- Require is_dir() before onboarding paths
- Reject pasted URLs early with a 'clone first' hint instead of letting them reach add_project
- Resolve symlinks with canonicalize so file symlinks fail the directory check
When it happens
Trigger: add_project pointed at a file (e.g. the .git file of a worktree, a symlink to a file, or the repo URL pasted instead of a path); a symlink whose target is a regular file; selecting the repo's .git file in a picker.
Common situations: Users pasting a URL or archive file instead of the cloned folder; drag-and-drop dropping a file; paths like repo/.git or repo/.git/config passed by tooling that assumed file-based selection.
Related errors
- project path not found
- no .git directory found in repository
- ProjectMissing
- project already exists
- bare repositories are not supported
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/035a778e7ac88ce5.
Report an issue: GitHub.