jdx/mise · error
timer unit '{name}' must set at least one of `on_boot_sec`,
Error message
timer unit '{name}' must set at least one of `on_boot_sec`, `on_unit_active_sec`, `on_unit_inactive_sec`, or `on_calendar` What it means
A unit classified as a Timer must specify at least one scheduling directive. If none of `on_boot_sec`, `on_unit_active_sec`, `on_unit_inactive_sec`, or `on_calendar` is set, `from_toml` bails because the timer would never trigger.
Source
Thrown at src/system/systemd.rs:212
(config.restart_sec.is_some(), "restart_sec"),
(config.standard_output.is_some(), "standard_output"),
(config.standard_error.is_some(), "standard_error"),
]
.into_iter()
.filter_map(|(is_set, field)| is_set.then_some(field))
.collect::<Vec<_>>();
if !service_only_fields.is_empty() {
bail!(
"timer unit '{name}' cannot set service-only directive(s): {}",
service_only_fields.join(", ")
);
}
if config.on_boot_sec.is_none()
&& config.on_unit_active_sec.is_none()
&& config.on_unit_inactive_sec.is_none()
&& config.on_calendar.is_none()
{
bail!(
"timer unit '{name}' must set at least one of `on_boot_sec`, \
`on_unit_active_sec`, `on_unit_inactive_sec`, or `on_calendar`"
);
}
}
if let Some(nice) = config.nice
&& !(-20..=19).contains(&nice)
{
bail!("service unit '{name}' has invalid `nice` value {nice}; expected -20 through 19");
}
if let Some(umask) = &config.umask
&& !valid_umask(umask)
{
bail!(
"service unit '{name}' has invalid `umask` value '{umask}'; expected an octal access mask from 0000 through 0777"
);
}
let wanted_by = config.wanted_by.unwrap_or_else(|| match kind {View on GitHub (pinned to afd2eddd3a)
Solutions
- Add a schedule, e.g. `on_calendar = "daily"` or `on_boot_sec = 30`.
- Use `on_unit_active_sec`/`on_unit_inactive_sec` for relative periodic triggers.
- If no schedule is wanted, make it a plain service instead by adding `exec_start` and removing timer-only fields like `randomized_delay_sec`.
Example fix
# before [systemd.backup.timer] # no schedule set # after [systemd.backup.timer] on_calendar = "*-*-* 03:00:00"
Defensive patterns
Strategy: validation
Validate before calling
const TIMER_KEYS: &[&str] = &["on_boot_sec", "on_unit_active_sec", "on_unit_inactive_sec", "on_calendar"];
fn timer_has_schedule(cfg: &toml::Value) -> bool {
TIMER_KEYS.iter().any(|k| cfg.get(k).is_some())
} Prevention
- Every timer block needs one of the four schedule keys — add a CI lint for this.
- Don't leave schedule keys commented out in committed configs.
- Start from a template that includes `on_calendar`.
When it happens
Trigger: Declaring `[systemd.<name>.timer]` (or a unit with a service-only-field-free config that had `randomized_delay_sec`, making kind Timer) without any of the four schedule keys.
Common situations: Creating a timer skeleton and forgetting the schedule; commenting out `on_calendar` while testing; copying a service block and only renaming it `.timer`.
Understand the failure class
Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.
Related errors
- service unit '{name}' must set a non-empty `exec_start`
- timer unit '{name}' cannot set service-only directive(s): {}
- "{path_raw}".{id}: no recognized operation (block, source, o
- brew-cask: command_wrapper requires content or executable
- bootstrap service '{name}' cannot be both masked and running
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/17fe54615ee8cf4e.
Report an issue: GitHub.