windmill-labs/windmill · error
Path '{}' exists and is not a directory
Error message
Path '{}' exists and is not a directory What it means
create_empty_dir prepares a fresh target directory used by archive fetch and git clone paths. If the path already exists but is a regular file, symlink-to-file, or other non-directory entry, it cannot create the directory there and returns io::ErrorKind::AlreadyExists with this message. It exists so callers fail fast instead of clobbering an unexpected file.
Source
Thrown at backend/windmill-worker/src/ansible_executor.rs:401
Ok(commit_hash)
}
pub fn create_empty_dir(path: &PathBuf) -> std::io::Result<()> {
if path.exists() {
if path.is_dir() {
let mut entries = std::fs::read_dir(&path)?;
if entries.next().is_some() {
return Err(std::io::Error::new(
std::io::ErrorKind::AlreadyExists,
format!(
"Directory '{}' already exists and is not empty",
path.display()
),
));
}
Ok(())
} else {
Err(std::io::Error::new(
std::io::ErrorKind::AlreadyExists,
format!("Path '{}' exists and is not a directory", path.display()),
))
}
} else {
std::fs::create_dir_all(path)
}
}
/// Signals a detached `spawn_blocking` task that the future awaiting it is
/// gone, so it can stop instead of running to completion in the background.
struct AbortOnDrop(std::sync::Arc<std::sync::atomic::AtomicBool>);
impl Drop for AbortOnDrop {
fn drop(&mut self) {
self.0.store(true, std::sync::atomic::Ordering::Relaxed);
}
}View on GitHub (pinned to e474e8803c)
Solutions
- Inspect the path in the message and delete or rename the offending file (rm '<path>' or mv it aside), then re-run the job.
- Ensure the target directory passed to fetch_repo_archive/clone_repo_without_history is dedicated and empty; pick a different target path if the file must be kept.
- In calling code, check path.is_dir() first and handle the AlreadyExists kind explicitly (e.g. remove_file then retry create_empty_dir).
Example fix
// before
create_empty_dir(&target)?;
// after
if target.exists() && !target.is_dir() {
std::fs::remove_file(&target)?;
}
create_empty_dir(&target)?; Defensive patterns
Strategy: validation
Validate before calling
let path = std::path::Path::new("/target/dir");
if path.exists() && !path.is_dir() {
return Err(format!("{} exists and is not a directory; remove it first", path.display()));
} Type guard
fn is_usable_empty_dir(p: &std::path::Path) -> bool {
p.is_dir() && std::fs::read_dir(p).map(|mut d| d.next().is_none()).unwrap_or(false)
} Try / catch
match create_empty_dir(&path) {
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
std::fs::remove_file(&path)?;
create_empty_dir(&path)?;
}
other => other?,
} Prevention
- Dedicate a unique, job-scoped target directory per clone/fetch run
- Check path.is_dir() before invoking create_empty_dir
- Clean up stale artifacts from previous failed runs in the target path
When it happens
Trigger: Calling create_empty_dir(path) when path exists as a non-directory (regular file or symlink to one), e.g. via fetch_repo_archive or clone_repo_without_history when the workspace/clone target path is occupied by a file.
Common situations: A leftover file at the repo checkout path (e.g. a tarball, lock file, or previous failed run wrote a file where a directory was expected); bind-mounted or pre-created files in container paths; case-insensitive filesystems colliding with an existing file.
Related errors
- Directory '{}' already exists and is not empty
- could not create dir '{directory_path}': {e}
- Failed to set permissions to {}: {e}
- Couldn't write inventory: {}
- preprocessor function is missing
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/7fafd1f5afd09da1.
Report an issue: GitHub.