windmill-labs/windmill · error

could not create dir '{directory_path}': {e}

Error message

could not create dir '{directory_path}': {e}

What it means

Windmill workers and servers create local directories (job working dirs, caches, logs) under WINDMILL_DIR on startup or when handling jobs. `create_directory_async` builds the directory recursively (like `mkdir -p`) and panics with this message if the OS refuses, e.g. due to permissions, a read-only filesystem, or a path component being a file. The panic kills the calling task (worker startup or job run).

Source

Thrown at backend/windmill-common/src/utils.rs:503

    (per_page, offset)
}

pub async fn now_from_db<'c, E: sqlx::PgExecutor<'c>>(
    db: E,
) -> Result<chrono::DateTime<chrono::Utc>> {
    Ok(sqlx::query_scalar!("SELECT now()")
        .fetch_one(db)
        .warn_after_seconds_with_sql(1, "now_from_db".to_string())
        .await?
        .unwrap())
}

pub async fn create_directory_async(directory_path: &str) {
    AsyncDirBuilder::new()
        .recursive(true)
        .create(directory_path)
        .await
        .unwrap_or_else(|e| panic!("could not create dir '{}': {}", directory_path, e));
}

pub fn create_directory_sync(directory_path: &str) {
    SyncDirBuilder::new()
        .recursive(true)
        .create(directory_path)
        .unwrap_or_else(|e| panic!("could not create dir '{}': {}", directory_path, e));
}

#[track_caller]
pub fn not_found_if_none<T, U: AsRef<str>>(opt: Option<T>, kind: &str, name: U) -> Result<T> {
    if let Some(o) = opt {
        Ok(o)
    } else {
        let loc = Location::caller();
        Err(Error::NotFound(format!(
            "{} not found at name {} ({}:{})",
            kind,

View on GitHub (pinned to e474e8803c)

Solutions

  1. Check the inner error `{e}` in the message for the exact OS errno (permission denied, read-only fs, etc.)
  2. Verify WINDMILL_DIR points to a writable volume and is owned by the user the worker runs as
  3. Ensure the mount is not read-only (`mount | grep <path>` / volume ro flags in compose/k8s)
  4. Remove any regular file that occupies a path component of the target directory
  5. Run the container with the correct fsGroup/runAsUser so the volume is writable

Example fix

// before: worker running as user 1000 with root-owned volume
volumes:
  - worker-data:/tmp/windmill
// after: make volume writable
volumes:
  - worker-data:/tmp/windmill
# plus in k8s: securityContext.fsGroup: 1000 or chown 1000:1000 on the host dir
Defensive patterns

Strategy: validation

Validate before calling

const fs = require('fs');
function assertWritableDirParent(dir) {
  let p = require('path').dirname(dir);
  while (p !== '/' && !fs.existsSync(p)) p = require('path').dirname(p);
  fs.accessSync(p, fs.constants.W_OK); // throws if not writable
}
assertWritableDirParent(process.env.WINDMILL_DIR || '/tmp/windmill');

Try / catch

// Wrap any custom automation that creates Windmill dirs
try {
  await fs.promises.mkdir(target, { recursive: true });
} catch (e) {
  if (e.code === 'EACCES' || e.code === 'EROFS') {
    console.error(`Fix volume permissions/read-only mount for ${target}: ${e.message}`);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling `create_directory_async(dir)` where the async tokio directory builder returns an error: parent path is a file, mount point read-only, EACCES on the target path, disk full, or invalid characters in the path.

Common situations: WINDMILL_DIR volume mounted read-only; container running as non-root but volume owned by root; Kubernetes emptyDir/persistence misconfigured; a file exists where the directory should be; NFS/permission issues on shared storage.

Understand the failure class

Background: mkdir permission denied (EACCES): failed to create directory errors explained — this error's family across 32 libraries.

Related errors


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