openai/codex · error · io::Error

InvalidData

InvalidData

Error message

Managed config file {} has no parent directory

What it means

Thrown by merge_managed_config_for_discovery while overlaying the managed-config layer for project-root/trust discovery. The loader resolves relative paths inside the managed config against the config file's parent directory (resolve_relative_paths_in_config_toml), and Path::parent() only returns None when the path is empty or the filesystem root itself. In practice this InvalidData guard means the managed config file path degenerated to '/' (or a bare filename) instead of a real file like /etc/codex/managed_config.toml.

Source

Thrown at codex-rs/config/src/loader/project_discovery.rs:20

use super::layer_io::LoadedConfigLayers;
use super::resolve_relative_paths_in_config_toml;
use crate::merge_toml_values;
use std::io;
use std::path::Path;
use toml::Value as TomlValue;

/// Overlay the already-read managed sources at their normal precedence: file,
/// then MDM. Resolve paths as the final loader does, without changing the raw
/// layers or reading either source again.
pub(super) fn merge_managed_config_for_discovery(
    discovery_config: &mut TomlValue,
    loaded: &LoadedConfigLayers,
    codex_home: &Path,
) -> io::Result<()> {
    if let Some(config) = &loaded.managed_config {
        let base_dir = config.file.as_path().parent().ok_or_else(|| {
            io::Error::new(
                io::ErrorKind::InvalidData,
                format!(
                    "Managed config file {} has no parent directory",
                    config.file.as_path().display()
                ),
            )
        })?;
        let resolved =
            resolve_relative_paths_in_config_toml(config.managed_config.clone(), base_dir)?;
        merge_toml_values(discovery_config, &resolved);
    }
    if let Some(config) = &loaded.managed_config_from_mdm {
        let resolved =
            resolve_relative_paths_in_config_toml(config.managed_config.clone(), codex_home)?;
        merge_toml_values(discovery_config, &resolved);
    }
    Ok(())
}

View on GitHub (pinned to 339751715c)

Solutions

  1. Point managed_config_path at a concrete file inside a directory, e.g. /etc/codex/managed_config.toml or <tmpdir>/managed_config.toml — never '/', a directory, or a bare file name
  2. When building the path programmatically, assert the base directory is non-empty and not '/' before joining the file name
  3. If you intended no managed config, remove the file or the override — a missing managed config is tolerated (layer skipped); only a parent-less path is fatal

Example fix

// before: override degenerates to the filesystem root
overrides.managed_config_path = Some(PathBuf::from("/"));

// after: a real file under a directory
overrides.managed_config_path = Some(PathBuf::from("/etc/codex/managed_config.toml"));
Defensive patterns

Strategy: validation

Validate before calling

// Run before building LoaderOverrides / loading config.
use std::path::Path;

fn managed_config_path_is_usable(path: &Path) -> bool {
    // Path::parent() is None exactly for "" and "/" — the cases the loader rejects.
    path.is_absolute() && path.parent().is_some()
}

if let Some(p) = &overrides.managed_config_path {
    assert!(managed_config_path_is_usable(p), 
        "managed config path must be an absolute file path, got {}", p.display());
}

Try / catch

match merge_managed_config_for_discovery(&mut discovery_config, &loaded, codex_home) {
    Err(e) if e.kind() == io::ErrorKind::InvalidData
        && e.to_string().contains("no parent directory") => {
        // The managed config path is '/' or empty — fix the override and reload.
    }
    result => result?,
}

Prevention

When it happens

Trigger: The managed config path (LoaderOverrides.managed_config_path override, or the default /etc/codex/managed_config.toml on Unix and CODEX_HOME/managed_config.toml on Windows) resolves to exactly '/' (or an empty/bare filename), that path exists and parses as TOML, and merge_managed_config_for_discovery runs during discovery. Any deeper absolute path has a parent directory and never triggers this.

Common situations: Test fixtures that join a file name onto a base-dir variable that is empty or '/' so the path collapses to a root; scripts building the override from an env var that expands to '/'; CI containers where the config-dir variable is set but the file name is empty. Users on the default path cannot hit it.

Related errors


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