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.

Solutions

  1. Inspect the path in the message and delete or rename the offending file (rm '<path>' or mv it aside), then re-run the job.
  2. 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.
  3. 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

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


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/7fafd1f5afd09da1. Report an issue: GitHub.

Appendix: 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)