affaan-m/ECC · error · anyhow::Error
legacy scheduler jobs file must be a JSON object or array: {
Error message
legacy scheduler jobs file must be a JSON object or array: {} What it means
Thrown by load_legacy_schedule_drafts when the parsed cron/jobs.json is a JSON value that is neither an object nor an array at the top level (i.e. a string, number, boolean, or null). The importer accepts an array of jobs, an object, or an object with a jobs/schedules/tasks array key; any other top-level scalar is rejected because it cannot yield schedule entries.
Source
Thrown at ecc2/src/main.rs:5531
let source_path = jobs_path
.strip_prefix(source)
.unwrap_or(&jobs_path)
.display()
.to_string();
let entries: Vec<&serde_json::Value> = match &value {
serde_json::Value::Array(items) => items.iter().collect(),
serde_json::Value::Object(map) => {
if let Some(items) = ["jobs", "schedules", "tasks"]
.iter()
.find_map(|key| map.get(*key).and_then(serde_json::Value::as_array))
{
items.iter().collect()
} else {
vec![&value]
}
}
_ => anyhow::bail!(
"legacy scheduler jobs file must be a JSON object or array: {}",
jobs_path.display()
),
};
Ok(entries
.into_iter()
.enumerate()
.map(|(index, value)| build_legacy_schedule_draft(value, index, &source_path))
.collect())
}
fn load_legacy_remote_dispatch_drafts(source: &Path) -> Result<Vec<LegacyRemoteDispatchDraft>> {
let gateway_dir = source.join("gateway");
if !gateway_dir.is_dir() {
return Ok(Vec::new());
}
View on GitHub (pinned to 01e15490f0)
Solutions
- Open cron/jobs.json and confirm it is a JSON object or array (e.g. '{"jobs":[...]}' or '[...]').
- If the schedules live in JSONL, rename/move the file so it is not picked up as jobs.json, or wrap the entries in an array.
- Validate the file with 'jq . cron/jobs.json' before running the importer; jq will surface structural problems.
- Convert a scalar/null file to '{"jobs":[]}' if no schedules exist yet.
Example fix
// before (cron/jobs.json)
"nightly-reindex"
// after (cron/jobs.json)
{
"jobs": [
{ "name": "nightly-reindex", "cron": "0 2 * * *", "task": "reindex" }
]
} Defensive patterns
Strategy: validation
Validate before calling
fn validate_jobs_json(text: &str) -> anyhow::Result<()> {
let v: serde_json::Value = serde_json::from_str(text)?;
if !v.is_array() && !v.is_object() {
anyhow::bail!("jobs file must be a JSON object or array");
}
Ok(())
} Type guard
fn is_jobs_container(v: &serde_json::Value) -> bool {
v.is_array()
|| v.as_object().map_or(false, |m| {
["jobs", "schedules", "tasks"].iter().any(|k| m.get(*k).map_or(false, |x| x.is_array()))
})
} Prevention
- Run 'jq . cron/jobs.json' before importing to catch structural problems.
- Keep jobs.json as an object with a 'jobs' array for clarity.
When it happens
Trigger: The legacy cron/jobs.json file contains a bare scalar such as '5', 'true', 'null', or a quoted string instead of a JSON object or array. Also triggered if the file was overwritten by a log line or a misconfigured tool that wrote a non-structured value.
Common situations: A hand-edited jobs.json saved as a string by mistake; a JSON Lines file renamed to jobs.json; a config generator that wrote a single scalar value; an empty/null top-level value.
Related errors
- invalid cron expression `{trimmed}`: expected 5, 6, or 7 fie
- Legacy workspace source must be a directory: {}
- ECC_PROJECT_DIR must be a child path within /workspace.
- Unknown argument: ${arg}
- ${source} is not valid JSON: ${error.message}
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/19578188ee655aae.
Report an issue: GitHub.