tinyhumansai/openhuman · critical
Config path is a directory, not a file: {}
Error message
Config path is a directory, not a file: {} What it means
During config load (`load_or_init` read branch), a directory at the config path is a bad-install/corruption signal, not a transient read failure — the loader fails fast with this distinct wording before attempting any read. The comment is explicit about why the wording matters: on Windows, `read_to_string` on a directory returns the same `Access is denied. (os error 5)` shape as a real ACL denial, which the observability classifier would otherwise demote as an expected user-state config-read failure; distinct wording keeps it paging (#3962, Codex P2).
Source
Thrown at src/openhuman/config/schema/load/impl_load.rs:373
error = %e,
"[config] failed to auto-fix config file permissions to 600",
);
}
}
}
}
}
}
// A directory (or other non-regular file) at the config path is a
// bad-install / corruption signal, not a transient read failure. On
// Windows `read_to_string` of a directory returns the same
// `Access is denied. (os error 5)` shape as a real ACL denial, which
// the observability classifier would otherwise demote. Fail fast with
// distinct wording so it keeps paging instead of being suppressed as
// an expected user-state config-read failure (#3962, Codex P2).
if config_path.is_dir() {
anyhow::bail!(
"Config path is a directory, not a file: {}",
config_path.display()
);
}
// Use the recovery-aware read path. If the file cannot be read
// (e.g. non-UTF-8 bytes), the corrupted file is renamed to
// `.corrupted.<timestamp>` and backup/defaults are attempted,
// with rate-limited error logging (#5167).
let (contents, read_was_recovered) =
read_config_with_recovery_or_default(&config_path).await?;
// When `read_config_with_recovery_or_default` returned an empty
// string (both primary and backup were unreadable), skip the TOML
// parse and use `Config::default()` directly. An empty TOML would
// otherwise parse successfully with serde defaults (all fields at
// their `Option::None` / `vec![]` / `false` values) instead of
// the richer `Default` impl (issue #5167).View on GitHub (pinned to 7491200858)
Solutions
- Inspect and remove the directory at the reported path (its contents may explain how it got there or hold a lost config), then let the core recreate a default `config.toml` or restore the file from backup.
- Restore config.toml from your backup or a `*.corrupted.*`/defaults copy if the directory replaced a real file.
- Fix the provisioning script/installer that created a directory at a file path.
Example fix
# before — provisioning created a directory at the config path $ ls -la ~/.openhuman/config.toml drwxr-xr-x 2 user user 4096 config.toml/ # after $ mv ~/.openhuman/config.toml ~/config.toml.dir-bad # core now creates a fresh config.toml, or restore yours from backup
Defensive patterns
Strategy: validation
Validate before calling
let cfg = home.join(".openhuman").join("config.toml");
if cfg.is_dir() {
// bad install — quarantine the directory so the core can recreate a default file
std::fs::rename(&cfg, cfg.with_extension("toml.dir-quarantined"))?;
} Type guard
fn config_path_is_loadable(p: &std::path::Path) -> bool {
!p.exists() || p.is_file()
} Prevention
- Provisioning scripts must create the config path as a file, never mkdir it
- Health-check installs: config.toml must be a regular file
- Restore configs from backups that preserve the file type
When it happens
Trigger: Something created a directory where `config.toml` should be — a mistaken `mkdir` in provisioning, an installer/restore bug, or a sync tool that materialized the path as a folder. `config_path.is_dir()` then trips before the recovery-aware read path runs.
Common situations: Failed or partially-restored installs; provisioning scripts that pre-create the config path as a directory; cloud-sync folders turning the file into a folder; misconfigured HOME resolving somewhere unexpected.
Related errors
- Working directory '{}' is not a directory. Set a valid path
- Failed to atomically persist active workspace marker {}: {er
- Failed to atomically replace config file: {e}
- Failed to atomically persist active user state {}: {error}
- OPENAI_CODEX_OAUTH_MISSING_AUTH_URL
AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17).
Data as JSON: /api/errors/2c11db66fe6a6835.
Report an issue: GitHub.