openai/codex · error · io::Error

Config file {} has no parent directory

Error message

Config file {} has no parent directory

What it means

validate_config_toml_strictly (codex-rs/config/src/loader/layer_io.rs:192) needs the config file's parent directory to install an AbsolutePathBufGuard, the base against which relative paths in the TOML are resolved. If Path::parent() returns None (the path is a filesystem root like / or the empty string), it returns ErrorKind::InvalidData with this message. read_config_from_path reaches it whenever strict config checking is enabled.

Source

Thrown at codex-rs/config/src/loader/layer_io.rs:198

            } else {
                tracing::debug!("{} not found", path.as_path().display());
            }
            Ok(None)
        }
        Err(err) => {
            tracing::error!("Failed to read {}: {err}", path.as_path().display());
            Err(err)
        }
    }
}

fn validate_config_toml_strictly(
    path: &AbsolutePathBuf,
    contents: &str,
    value: &TomlValue,
) -> io::Result<()> {
    let Some(base_dir) = path.as_path().parent() else {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            format!("Config file {} has no parent directory", path.display()),
        ));
    };
    let _guard = AbsolutePathBufGuard::new(base_dir);
    if let Some(config_error) = config_error_from_ignored_toml_value_fields::<ConfigToml>(
        path.as_path(),
        contents,
        value.clone(),
    ) {
        return Err(io_error_from_config_error(
            io::ErrorKind::InvalidData,
            config_error,
            /*source*/ None,
        ));
    }

    Ok(())

View on GitHub (pinned to 339751715c)

Solutions

  1. Point the config path at a real file inside a directory (e.g. /etc/codex/config.toml), never at a filesystem root or empty string
  2. Echo and fix the env var or CLI override that produced the degenerate path
  3. Guard path construction so empty dir variables fall back to a sane default before the filename is appended

Example fix

// before
let path = AbsolutePathBuf::try_from(env::var("CONFIG").unwrap_or_default())?; // "" or "/"

// after
let dir = env::var("CONFIG").unwrap_or_else(|_| "/etc/codex".into());
let path = AbsolutePathBuf::try_from(PathBuf::from(dir).join("config.toml"))?;
Defensive patterns

Strategy: validation

Validate before calling

let Some(_base) = path.as_path().parent() else {
    anyhow::bail!(
        "config path must be a file inside a directory, got: {}",
        path.display()
    );
};

Type guard

fn has_parent_dir(p: &std::path::Path) -> bool {
    p.parent().is_some()
}

Try / catch

match read_config_from_path(&path).await {
    Err(e) if e.kind() == io::ErrorKind::InvalidData
        && e.to_string().contains("no parent directory") =>
    {
        // fix the env var / override that produced the path; do not retry as-is
    }
    r => r?,
}

Prevention

When it happens

Trigger: read_config_from_path invoked in strict mode with a path whose parent is None: literally '/', a Windows drive root, or an empty path. Usually the result of a mis-set CODEX_HOME, a --config-path style override, or joining onto an empty directory variable.

Common situations: A wrapper script or env var resolves to / or empty; container images setting the config location to the filesystem root; config path construction that skips appending the filename when a variable is empty.

Related errors


AI-assisted analysis of openai/codex@339751715c (2026-08-25). Data as JSON: /api/errors/44e21abcb2337a2f. Report an issue: GitHub.