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

  1. Open cron/jobs.json and confirm it is a JSON object or array (e.g. '{"jobs":[...]}' or '[...]').
  2. 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.
  3. Validate the file with 'jq . cron/jobs.json' before running the importer; jq will surface structural problems.
  4. 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

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


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/19578188ee655aae. Report an issue: GitHub.