windmill-labs/windmill · error

WINDMILL_DIR must not be empty

Error message

WINDMILL_DIR must not be empty

What it means

Windmill derives all local storage paths (job dirs, cache, logs) from the WINDMILL_DIR environment variable (defaulting to a normalized temp dir when unset). At lazy-static initialization it validates the resolved value; if it resolves to an empty string the process panics immediately, since an empty root would produce invalid paths like '/logs'.

Source

Thrown at backend/windmill-common/src/worker.rs:831

    WORKER_PULL_QUERIES_FAIRNESS.store(std::sync::Arc::new(fairness_queries));
}

lazy_static::lazy_static! {
    pub static ref WINDMILL_DIR: String = {
        let dir = std::env::var("WINDMILL_DIR")
            .unwrap_or_else(|_| {
                #[cfg(not(windows))]
                { "/tmp/windmill".to_string() }
                #[cfg(windows)]
                {
                    let temp = std::env::temp_dir();
                    let temp_str = temp.to_string_lossy();
                    let normalized = temp_str.trim_end_matches(&['/', '\\'][..]).replace('\\', "/");
                    format!("{}/windmill", normalized)
                }
            });
        if dir.is_empty() {
            panic!("WINDMILL_DIR must not be empty");
        }
        if dir.ends_with('/') || dir.ends_with('\\') {
            panic!("WINDMILL_DIR must not end with a trailing slash, got: {dir}");
        }
        dir
    };
    pub static ref TMP_LOGS_DIR: String = format!("{}/logs", *WINDMILL_DIR);
    pub static ref ROOT_CACHE_DIR: String = format!("{}/cache/", *WINDMILL_DIR);
    pub static ref HUB_CACHE_DIR: String = format!("{}hub", *ROOT_CACHE_DIR);
    pub static ref HUB_RT_CACHE_DIR: String = format!("{}hub_rt", *ROOT_CACHE_DIR);
}

pub fn write_file(dir: &str, path: &str, content: &str) -> error::Result<File> {
    let path = format!("{}/{}", dir, path);
    let mut file = File::create(&path).map_err(|e| {
        tracing::error!("Failed to create file at {path}: {:?}", &e);
        e
    })?;

View on GitHub (pinned to e474e8803c)

Solutions

  1. Set WINDMILL_DIR to a non-empty absolute path (e.g. /tmp/windmill) or unset the variable entirely to use the temp-dir default
  2. Check for empty values injected by your orchestrator (k8s env, compose, systemd EnvironmentFile)
  3. Ensure your entrypoint script does not export an empty variable from an unset prerequisite

Example fix

// before
docker run -e WINDMILL_DIR= ... 
// after
docker run -e WINDMILL_DIR=/tmp/windmill ...
Defensive patterns

Strategy: validation

Validate before calling

const v = process.env.WINDMILL_DIR;
if (v === '') throw new Error('WINDMILL_DIR is set but empty; unset it or give it a value');

Type guard

function hasWindmillDir(env) {
  return typeof env.WINDMILL_DIR === 'string' && env.WINDMILL_DIR.length > 0;
}

Prevention

When it happens

Trigger: Setting `WINDMILL_DIR=` (empty value) in the environment of a server/worker/indexer process; some orchestrators or secret injectors export the variable with an empty string.

Common situations: Kubernetes ConfigMap/secret with an empty value for the key; docker-compose `WINDMILL_DIR: ""`; shell script doing `export WINDMILL_DIR=$UNSET_VAR` where the var is empty.

Related errors


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