{"record":{"id":"12b19217b566506e","repo":"openai/codex","slug":"invaliddata-12b192","errorCode":"InvalidData","errorMessage":"Managed config file {} has no parent directory","messagePattern":"Managed config file (.+?) has no parent directory","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"codex-rs/config/src/loader/project_discovery.rs","lineNumber":20,"sourceCode":"\nuse super::layer_io::LoadedConfigLayers;\nuse super::resolve_relative_paths_in_config_toml;\nuse crate::merge_toml_values;\nuse std::io;\nuse std::path::Path;\nuse toml::Value as TomlValue;\n\n/// Overlay the already-read managed sources at their normal precedence: file,\n/// then MDM. Resolve paths as the final loader does, without changing the raw\n/// layers or reading either source again.\npub(super) fn merge_managed_config_for_discovery(\n    discovery_config: &mut TomlValue,\n    loaded: &LoadedConfigLayers,\n    codex_home: &Path,\n) -> io::Result<()> {\n    if let Some(config) = &loaded.managed_config {\n        let base_dir = config.file.as_path().parent().ok_or_else(|| {\n            io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(\n                    \"Managed config file {} has no parent directory\",\n                    config.file.as_path().display()\n                ),\n            )\n        })?;\n        let resolved =\n            resolve_relative_paths_in_config_toml(config.managed_config.clone(), base_dir)?;\n        merge_toml_values(discovery_config, &resolved);\n    }\n    if let Some(config) = &loaded.managed_config_from_mdm {\n        let resolved =\n            resolve_relative_paths_in_config_toml(config.managed_config.clone(), codex_home)?;\n        merge_toml_values(discovery_config, &resolved);\n    }\n    Ok(())\n}","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/openai/codex/blob/339751715c64496cb86246bfb3935f40e309dd3d/codex-rs/config/src/loader/project_discovery.rs#L2-L38","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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","When building the path programmatically, assert the base directory is non-empty and not '/' before joining the file name","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"],"exampleFix":"// before: override degenerates to the filesystem root\noverrides.managed_config_path = Some(PathBuf::from(\"/\"));\n\n// after: a real file under a directory\noverrides.managed_config_path = Some(PathBuf::from(\"/etc/codex/managed_config.toml\"));","handlingStrategy":"validation","validationCode":"// Run before building LoaderOverrides / loading config.\nuse std::path::Path;\n\nfn managed_config_path_is_usable(path: &Path) -> bool {\n    // Path::parent() is None exactly for \"\" and \"/\" — the cases the loader rejects.\n    path.is_absolute() && path.parent().is_some()\n}\n\nif let Some(p) = &overrides.managed_config_path {\n    assert!(managed_config_path_is_usable(p), \n        \"managed config path must be an absolute file path, got {}\", p.display());\n}","typeGuard":null,"tryCatchPattern":"match merge_managed_config_for_discovery(&mut discovery_config, &loaded, codex_home) {\n    Err(e) if e.kind() == io::ErrorKind::InvalidData\n        && e.to_string().contains(\"no parent directory\") => {\n        // The managed config path is '/' or empty — fix the override and reload.\n    }\n    result => result?,\n}","preventionTips":["Never set the managed-config override to a directory, '/', or an empty string — always a concrete .toml file path","Assert base directories are non-empty before joining file names when constructing config paths from variables","Remember NotFound on the managed config file is fine (layer skipped); only a parent-less path is an error"],"tags":["config","managed-config","path","rust","filesystem"],"backgroundTag":"invalid-config-file-path","analyzedSha":"339751715c64496cb86246bfb3935f40e309dd3d","analyzedAt":"2026-08-25T05:35:09.876Z","schemaVersion":2},"datasetVersion":"2026-08-25T06:17:31.827Z"}