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
- Check the job's schedule type first (cron list shows schedule details) and only send schedule flags that match it.
- 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).
- 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
- Record each job's flavor next to its id in runbooks so schedule updates target the right variant.
- In bulk scripts, branch on schedule type before choosing --expression/--tz vs interval/at flags.
- Treat type conversion as delete+recreate, never as a patch.
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
- cli-cron-update-no-field
- --allowed-tool is only supported for agent cron jobs
- --allowed-tool is only supported with --prompt cron jobs
- Unknown agent {agent_alias:?} (no [agents.{agent_alias}] ent
- cli-skills-install-skill-requires-git
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/88aa8e0bb53027a0.
Report an issue: GitHub.