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.

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.

Appendix: 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)