windmill-labs/windmill · error

Directory '{}' already exists and is not empty

Error message

Directory '{}' already exists and is not empty

What it means

`create_empty_dir` prepares a directory that must be completely empty before a repo is cloned/unpacked into it. If the path already exists as a directory containing at least one entry, it refuses to proceed with `io::ErrorKind::AlreadyExists`, preventing the executor from mixing new repo contents with stale files. It is called by `fetch_repo_archive` and `clone_repo_without_history`.

Source

Thrown at backend/windmill-worker/src/ansible_executor.rs:391

    if !commit_hash_output.status.success() {
        let stderr = String::from_utf8(commit_hash_output.stderr)?;
        return Err(anyhow!("Error getting git repo commit hash: {stderr}").into());
    }

    let commit_hash = String::from_utf8(commit_hash_output.stdout)?
        .trim()
        .to_string();

    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)
    }
}

View on GitHub (pinned to e474e8803c)

Solutions

  1. Delete the stale directory contents (e.g. `rm -rf <path>`) and retry the repo sync/fetch job
  2. Check the job logs to see which path failed and whether a previous run crashed mid-fetch; clean up its leftovers
  3. If this recurs, inspect the storage key/path generation for the repo so distinct syncs don't share one directory
  4. As code hardening, replace-or-clean the target dir before calling `clone_repo_without_history`/`fetch_repo_archive`

Example fix

// before
let dir = worker_paths.ansible_repo_dir(workspace_id, &path); // leftover files -> AlreadyExists
std::fs::create_dir_all(&dir)?; // doesn't help, dir exists non-empty
// after
if dir.exists() {
    std::fs::remove_dir_all(&dir)?;
}
std::fs::create_dir_all(&dir)?;
Defensive patterns

Strategy: validation

Validate before calling

fn ensure_empty_dir(path: &std::path::Path) -> std::io::Result<()> {
    if path.exists() {
        let has_entries = std::fs::read_dir(path)?.next().is_some();
        if has_entries {
            std::fs::remove_dir_all(path)?; // or fail with a clear operator-facing message
        }
    }
    std::fs::create_dir_all(path)
}

Type guard

fn is_nonexistent_or_empty_dir(path: &std::path::Path) -> bool {
    !path.exists()
        || (path.is_dir()
            && std::fs::read_dir(path).map(|mut d| d.next().is_none()).unwrap_or(false))
}

Try / catch

match create_empty_dir(&repo_dir) {
    Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
        tracing::warn!("stale repo dir {}, cleaning and retrying", repo_dir.display());
        std::fs::remove_dir_all(&repo_dir)?;
        create_empty_dir(&repo_dir)?;
    }
    Err(e) => return Err(e.into()),
    Ok(_) => {}
}

Prevention

When it happens

Trigger: Calling `create_empty_dir(path)` (transitively via `fetch_repo_archive` or `clone_repo_without_history`, e.g. an Ansible repo sync in windmill-worker) when `path` exists, is a directory, and `read_dir` yields at least one entry.

Common situations: A previous sync/fetch failed midway and left partial files; leftover directory from an old run after a failed job; someone manually placed files in the target path; worker restart after crash without cleanup; path collision from a bad storage key (e.g. workspace/path reused across syncs).

Related errors


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