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

Cannot update expression/tz on a non-cron schedule

Error message

Cannot update expression/tz on a non-cron schedule

What it means

When `cron update` receives --expression or --tz, it loads the existing job to merge the new schedule. Those flags only make sense for jobs whose schedule is Schedule::Cron; if the stored job's schedule is a different variant (Schedule::At one-shot or Schedule::Every interval), the merge is refused with this error and the job is left untouched.

Source

Thrown at src/cron/mod.rs:584

                validate_delivery_config(merged.as_ref())?;
                merged
            } else {
                None
            };

            // Merge expression/tz with the existing schedule so that
            // --tz alone updates the timezone and --expression alone
            // preserves the existing timezone.
            let schedule = if expression.is_some() || tz.is_some() {
                let existing = existing
                    .as_ref()
                    .expect("existing job must be loaded when updating schedule");
                let (existing_expr, existing_tz) = match &existing.schedule {
                    Schedule::Cron {
                        expr,
                        tz: existing_tz,
                    } => (expr.clone(), existing_tz.clone()),
                    _ => bail!("Cannot update expression/tz on a non-cron schedule"),
                };
                Some(Schedule::Cron {
                    expr: expression.unwrap_or(existing_expr),
                    tz: tz.or(existing_tz),
                })
            } else {
                None
            };

            if !allowed_tools.is_empty() {
                let existing = existing
                    .as_ref()
                    .expect("existing job must be loaded when updating allowed tools");
                if existing.job_type != JobType::Agent {
                    bail!("--allowed-tool is only supported for agent cron jobs");
                }
            }

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Check the job's schedule type first (cron list shows schedule details) and only send schedule flags that match it.
  2. To convert a one-shot or interval job into a cron-expression job, delete it and re-create it with `cron add --expression ...` (ids do not transfer).
  3. For interval jobs, update the interval field through its own flag rather than --expression.

Example fix

# before: patching expression on a one-shot job
zeroclaw cron update abc123 --expression '0 5 * * *'   # job was created with add-at
# after: recreate as a cron job
zeroclaw cron remove abc123
zeroclaw cron add bot --expression '0 5 * * *' --command 'report.sh'
Defensive patterns

Strategy: validation

Validate before calling

// before `cron update <id> --expression/--tz`, confirm the job is cron-scheduled
// `zeroclaw cron list` prints each job's schedule; alternatively read the store:
fn is_cron_job(schedule_kind: &str) -> bool {
    schedule_kind == "cron" // reject before invoking update otherwise
}

Try / catch

match run_cron_update(args).await {
    Err(e) if e.to_string().contains("non-cron schedule") => {
        // job is At/Every: recreate it as a cron job instead of patching
    }
    other => other,
}

Prevention

When it happens

Trigger: `zeroclaw cron update <id> --expression '0 * * * *'` or `--tz Europe/Berlin` on a job originally created via cron add-at, add-every, or once (delay one-shot) rather than the expression-based add.

Common situations: Users forgetting which job flavor an id refers to; scripts blindly patching --expression/--tz across every job id; trying to convert a one-shot to recurring by updating its expression.

Related errors


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