{"record":{"id":"9c01d18a8b8858fe","repo":"windmill-labs/windmill","slug":"directory-already-exists-and-is-not-empty","errorCode":null,"errorMessage":"Directory '{}' already exists and is not empty","messagePattern":"Directory '(.+?)' already exists and is not empty","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/windmill-worker/src/ansible_executor.rs","lineNumber":391,"sourceCode":"\n    if !commit_hash_output.status.success() {\n        let stderr = String::from_utf8(commit_hash_output.stderr)?;\n        return Err(anyhow!(\"Error getting git repo commit hash: {stderr}\").into());\n    }\n\n    let commit_hash = String::from_utf8(commit_hash_output.stdout)?\n        .trim()\n        .to_string();\n\n    Ok(commit_hash)\n}\n\npub fn create_empty_dir(path: &PathBuf) -> std::io::Result<()> {\n    if path.exists() {\n        if path.is_dir() {\n            let mut entries = std::fs::read_dir(&path)?;\n            if entries.next().is_some() {\n                return Err(std::io::Error::new(\n                    std::io::ErrorKind::AlreadyExists,\n                    format!(\n                        \"Directory '{}' already exists and is not empty\",\n                        path.display()\n                    ),\n                ));\n            }\n            Ok(())\n        } else {\n            Err(std::io::Error::new(\n                std::io::ErrorKind::AlreadyExists,\n                format!(\"Path '{}' exists and is not a directory\", path.display()),\n            ))\n        }\n    } else {\n        std::fs::create_dir_all(path)\n    }\n}","sourceCodeStart":373,"sourceCodeEnd":409,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/backend/windmill-worker/src/ansible_executor.rs#L373-L409","documentation":"`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`.","triggerScenarios":"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.","commonSituations":"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).","solutions":["Delete the stale directory contents (e.g. `rm -rf <path>`) and retry the repo sync/fetch job","Check the job logs to see which path failed and whether a previous run crashed mid-fetch; clean up its leftovers","If this recurs, inspect the storage key/path generation for the repo so distinct syncs don't share one directory","As code hardening, replace-or-clean the target dir before calling `clone_repo_without_history`/`fetch_repo_archive`"],"exampleFix":"// before\nlet dir = worker_paths.ansible_repo_dir(workspace_id, &path); // leftover files -> AlreadyExists\nstd::fs::create_dir_all(&dir)?; // doesn't help, dir exists non-empty\n// after\nif dir.exists() {\n    std::fs::remove_dir_all(&dir)?;\n}\nstd::fs::create_dir_all(&dir)?;","handlingStrategy":"validation","validationCode":"fn ensure_empty_dir(path: &std::path::Path) -> std::io::Result<()> {\n    if path.exists() {\n        let has_entries = std::fs::read_dir(path)?.next().is_some();\n        if has_entries {\n            std::fs::remove_dir_all(path)?; // or fail with a clear operator-facing message\n        }\n    }\n    std::fs::create_dir_all(path)\n}","typeGuard":"fn is_nonexistent_or_empty_dir(path: &std::path::Path) -> bool {\n    !path.exists()\n        || (path.is_dir()\n            && std::fs::read_dir(path).map(|mut d| d.next().is_none()).unwrap_or(false))\n}","tryCatchPattern":"match create_empty_dir(&repo_dir) {\n    Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {\n        tracing::warn!(\"stale repo dir {}, cleaning and retrying\", repo_dir.display());\n        std::fs::remove_dir_all(&repo_dir)?;\n        create_empty_dir(&repo_dir)?;\n    }\n    Err(e) => return Err(e.into()),\n    Ok(_) => {}\n}","preventionTips":["Clean up partially-fetched repo directories in the same code path that creates them (remove on fetch failure)","Never point repo syncs at directories that may contain user files; derive the path solely from storage keys","After a worker crash or kill -9, sweep stale repo dirs before resuming sync jobs","Keep one directory per (workspace, path) so concurrent syncs can't share a target"],"tags":["filesystem","io","ansible","worker"],"backgroundTag":"directory-not-empty","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}