zeroclaw-labs/zeroclaw · error · anyhow::Error

Declarative cron job '{id}': invalid job_type '{other}', exp

Error message

Declarative cron job '{id}': invalid job_type '{other}', expected 'shell' or 'agent'

What it means

validate_decl matches decl.job_type case-insensitively against exactly two accepted values: "shell" and "agent". Any other string (typo, a model name, a provider label) falls into the catch-all arm and the declarative sync aborts before any DB write.

Source

Thrown at crates/zeroclaw-runtime/src/cron/store.rs:1423

                    "Declarative cron job '{id}': shell job requires a non-empty 'command'"
                );
            }
        }
        "agent" => {
            if decl.prompt.as_deref().is_none_or(|p| p.trim().is_empty()) {
                anyhow::bail!(
                    "Declarative cron job '{id}': agent job requires a non-empty 'prompt'"
                );
            }
            if decl.shell_output_format != zeroclaw_config::schema::CronShellOutputFormat::default()
            {
                anyhow::bail!(
                    "Declarative cron job '{id}': shell_output_format is shell-only and cannot be set on an agent job"
                );
            }
        }
        other => {
            anyhow::bail!(
                "Declarative cron job '{id}': invalid job_type '{other}', expected 'shell' or 'agent'"
            );
        }
    }

    Ok(())
}

/// Convert a `CronScheduleDecl` to the runtime `Schedule` type.
fn convert_schedule_decl(decl: &zeroclaw_config::schema::CronScheduleDecl) -> Result<Schedule> {
    use zeroclaw_config::schema::CronScheduleDecl;
    match decl {
        CronScheduleDecl::Cron { expr, tz } => Ok(Schedule::Cron {
            expr: expr.clone(),
            tz: tz.clone(),
        }),
        CronScheduleDecl::Every { every_ms } => Ok(Schedule::Every {
            every_ms: *every_ms,

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Set job_type to exactly "shell" or "agent" (any capitalization works because matching is lowercased)
  2. Double-check for trailing characters or smart quotes when copy-pasting

Example fix

# before
[cron.cleanup]
job_type = "bash"
schedule = { kind = "cron", expression = "0 4 * * *" }
command = "rm -rf /tmp/cache/*"

# after
[cron.cleanup]
job_type = "shell"
schedule = { kind = "cron", expression = "0 4 * * *" }
command = "rm -rf /tmp/cache/*"
Defensive patterns

Strategy: type-guard

Validate before calling

for (id, decl) in &decls {
    let jt = decl.job_type.to_lowercase();
    if jt != "shell" && jt != "agent" {
        anyhow::bail!("cron job '{id}': job_type must be 'shell' or 'agent', got '{jt}'");
    }
}

Type guard

fn is_supported_cron_job_type(job_type: &str) -> bool {
    matches!(job_type.to_lowercase().as_str(), "shell" | "agent")
}

Try / catch

if let Err(err) = zeroclaw_runtime::cron::sync_declarative_jobs(&config, &decls) {
    if err.to_string().contains("invalid job_type") {
        eprintln!("config error: job_type must be 'shell' or 'agent' (case-insensitive)");
    }
    return Err(err);
}

Prevention

When it happens

Trigger: job_type = "sh", "bash", "command", "run", "Agent " with trailing garbage, or a model/provider identifier mistakenly placed in the job_type field.

Common situations: Assuming the scheduler accepts arbitrary runner names; pasting a model id into job_type; renaming from another tool's cron config that uses different type names.

Understand the failure class

Background: Config validation failed: what "invalid value for {key}" and settings-rejection errors mean across 19 open-source libraries — this error's family across 19 libraries.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/12ae93e1f44e396e. Report an issue: GitHub.