headroomlabs-ai/headroom · error · ConfigError
simulator config is not valid JSON: {0}
Error message
simulator config is not valid JSON: {0} What it means
Raised by load_config when the file was read successfully but serde_json::from_str fails to parse it as a SimulatorConfig. The #[from] attribute converts any serde_json::Error (syntax error or type mismatch against the struct) into ConfigError::Parse. The embedded message pinpoints the line/column of the offending JSON.
Source
Thrown at crates/headroom-simulators/src/config.rs:63
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)?)
}
fn default_status() -> u16 {
200
}
View on GitHub (pinned to 322425c43b)
Solutions
- Validate the file with an external parser first: jq . sim.json — any syntax error is reported with a position.
- Read the serde error message: 'expected struct SimulatorConfig' means a field type mismatch; fix the field named in the message.
- Cross-check against the struct definition in crates/headroom-simulators/src/config.rs — note sse entries require a data field (event is optional via #[serde(default)]).
- If you upgraded the crate recently, re-copy the example config from the new version and re-apply your changes.
Example fix
// before
{"sse": {"event": "x", "data": {}}}
// after
{"sse": [{"event": "x", "data": {"role": "assistant"}}]} Defensive patterns
Strategy: validation
Validate before calling
fn json_parses(raw: &str) -> Result<(), String> {
serde_json::from_str::<serde_json::Value>(raw)
.map(|_| ())
.map_err(|e| e.to_string())
}
// and schema check: the "sse" array entries need a "data" field
let v: serde_json::Value = serde_json::from_str(&raw)?;
assert!(v.get("sse").map_or(true, |s| s.as_array().is_some())); Try / catch
match serde_json::from_str::<SimulatorConfig>(&raw) {
Err(e) if e.classify() == serde_json::error::Category::Syntax => {
eprintln!("config is not valid JSON at line {}: fix syntax", e.line());
}
Err(e) => eprintln!("config field mismatch: {e}"),
Ok(cfg) => { /* proceed */ }
} Prevention
- Lint config files in CI with jq so syntax errors never reach runtime.
- Keep a checked-in example config that CI loads through load_config as a schema canary.
- Avoid JSONC-style comments/trailing commas in configs consumed by serde_json.
When it happens
Trigger: Passing a config file containing invalid JSON (trailing commas, comments, unquoted keys) or valid JSON that does not match SimulatorConfig's shape (e.g. 'sse' is not an array of {event?, data} objects).
Common situations: Hand-edited config with a trailing comma or JSONC comment, a YAML config fed where JSON is expected, or a schema drift after upgrading headroom-simulators (renamed/removed fields without #[serde(default)]).
Related errors
- invalid upstream URL: {0}
- compression engine startup failed: {0}
- failed to read simulator config {path}: {source}
- invalid pipeline config TOML: {0}
- recommendations file not found: {0}
AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15).
Data as JSON: /api/errors/a4d4409df38017a0.
Report an issue: GitHub.