headroomlabs-ai/headroom · error · ConfigError
failed to read simulator config {path}: {source}
Error message
failed to read simulator config {path}: {source} What it means
Raised by load_config in headroom-simulators when fs::read_to_string fails on the explicitly provided simulator config path. The std::io::Error source is preserved via #[source], so the underlying reason (NotFound, PermissionDenied, etc.) appears in the chain. It never fires when path is None — the function then returns SimulatorConfig::default().
Source
Thrown at crates/headroom-simulators/src/config.rs:57
pub headers: BTreeMap<String, String>,
#[serde(default)]
pub json: Option<Value>,
#[serde(default)]
pub body: Option<String>,
#[serde(default)]
pub sse: Vec<SseFrame>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SseFrame {
#[serde(default)]
pub event: Option<String>,
pub data: Value,
}
#[derive(Debug, Error)]
pub enum ConfigError {
#[error("failed to read simulator config {path}: {source}")]
Read {
path: String,
#[source]
source: std::io::Error,
},
#[error("simulator config is not valid JSON: {0}")]
Parse(#[from] serde_json::Error),
}
pub fn load_config(path: Option<&Path>) -> Result<SimulatorConfig, ConfigError> {
let Some(path) = path else {
return Ok(SimulatorConfig::default());
};
let raw = fs::read_to_string(path).map_err(|source| ConfigError::Read {
path: path.display().to_string(),
source,
})?;
Ok(serde_json::from_str(&raw)?)View on GitHub (pinned to 322425c43b)
Solutions
- Verify the path exists and is a regular file: ls -l <path> from the same working directory the binary runs in.
- Check read permission (chmod +r) and, in containers, that the file was actually COPY'd into the image.
- Pass an absolute path to load_config to avoid cwd-dependent resolution.
- If no custom simulator behavior is needed, call load_config(None) to get the default config instead of pointing at a missing file.
Example fix
// before
let cfg = load_config(Some(Path::new("sim.json")))?;
// after
let path = std::env::var_os("SIM_CONFIG")
.map(PathBuf::from)
.filter(|p| p.exists()); // fall back to defaults when absent
let cfg = load_config(path.as_deref())?; Defensive patterns
Strategy: validation
Validate before calling
use std::path::Path;
fn config_readable(path: &Path) -> bool {
path.is_file() && std::fs::metadata(path).map(|m| !m.permissions().readonly()).unwrap_or(false)
}
// before load_config:
if !config_readable(&path) { eprintln!("missing config {path}, using defaults"); } Type guard
fn as_valid_config_path(p: Option<&std::path::Path>) -> Option<&std::path::Path> {
p.filter(|x| x.is_file())
} Try / catch
match load_config(Some(&path)) {
Err(ConfigError::Read { path, source }) => {
eprintln!("cannot read {path}: {source}; falling back to defaults");
load_config(None)
}
other => other,
} Prevention
- Resolve config paths to absolute form before passing them in, so behavior does not depend on cwd.
- Add a startup doctor check that stats every configured file path and reports missing/unreadable ones.
- In containers, assert in the Dockerfile that the config was copied: test -f /etc/headroom/sim.json.
When it happens
Trigger: Calling load_config(Some(path)) where path does not exist, is a directory, has no read permission, or is otherwise unreadable at the OS level.
Common situations: Typos in the --simulator-config flag, pointing at a config file that was never committed to the container image, restrictive file modes in a slim Docker image, or a relative path resolved against an unexpected working directory.
Related errors
- invalid upstream URL: {0}
- compression engine startup failed: {0}
- simulator config is not valid JSON: {0}
- bedrock_upstream_exception
- bedrock_eventstream_missing_event_type
AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15).
Data as JSON: /api/errors/e70e871d47d0e32e.
Report an issue: GitHub.