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

The legacy scheduler import parses the jobs file as JSON and accepts only a top-level object or array; any other JSON top-level value (string, number, boolean, null) triggers this bail with the file path. The importer needs a collection shape to extract job entries.

Solutions

  1. Check the file's top-level value (`jq 'type' <file>`) and fix the export to produce an object or array of jobs.
  2. Point the import at the correct legacy jobs JSON file.
  3. If the export is `null` because there were no jobs, skip the import or replace the file with `[]`.

Example fix

// before (jobs.json)
null

// after (jobs.json)
[]
Defensive patterns

Strategy: validation

Validate before calling

// Check the JSON root shape before import
const jobs = JSON.parse(fs.readFileSync(jobsPath, 'utf8'));
if (jobs === null || typeof jobs !== 'object') {
  throw new Error(`jobs file root must be object or array: ${jobsPath}`);
}

Type guard

function isJobCollection(v) {
  return v !== null && typeof v === 'object' && !Array.isArray(v)
    ? true
    : Array.isArray(v);
}

Try / catch

match parse_legacy_jobs(&jobs_path) {
    Err(e) if e.to_string().contains("must be a JSON object or array") => {
        eprintln!("Fix the export or point at the correct jobs file: {}", jobs_path.display());
    }
    other => other?,
}

Prevention

When it happens

Trigger: Pointing the legacy schedule import at a JSON file whose root is a scalar (e.g. a file containing `{}` is fine, but `"jobs"` or `42` is not); feeding the wrong file (a settings file or scalar export) as the jobs file.

Common situations: An export tool wrote a scalar/null (e.g. when there were no jobs it wrote `null`); the user passed the wrong JSON file to the importer; an older export format with a different root shape.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/19578188ee655aae. Report an issue: GitHub.

Appendix: source

Thrown at ecc2/src/main.rs:5530

    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 8321021c54)