jdx/mise · error

timer unit '{name}' cannot set service-only directive(s): {}

Error message

timer unit '{name}' cannot set service-only directive(s): {}

What it means

Timer units in this config schema must only carry timer-related directives. If a unit is detected as a Timer (it has a schedule field) but also sets service-only directives (`exec_start`, `type`, `remain_after_exit`, `environment`, `standard_output`, `standard_error`, etc.), `from_toml` rejects it and lists the offending fields.

Source

Thrown at src/system/systemd.rs:202

                (config.timeout_start_sec.is_some(), "timeout_start_sec"),
                (config.timeout_stop_sec.is_some(), "timeout_stop_sec"),
                (config.no_new_privileges.is_some(), "no_new_privileges"),
                (config.private_tmp.is_some(), "private_tmp"),
                (!config.environment.is_empty(), "environment"),
                (!config.environment_file.is_empty(), "environment_file"),
                (config.nice.is_some(), "nice"),
                (config.umask.is_some(), "umask"),
                (config.working_directory.is_some(), "working_directory"),
                (config.restart.is_some(), "restart"),
                (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)
        {

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Remove the listed service-only directives from the timer block.
  2. Split into two units: a `[systemd.myapp]` service with `exec_start` and a `[systemd.myapp.timer]` (or similarly named) timer with the schedule.
  3. If you actually want a service, remove the schedule keys (`on_boot_sec`, `on_calendar`, etc.) so the unit is classified as a Service.

Example fix

# before
[systemd.myapp]
on_boot_sec = 60
exec_start = "/usr/bin/myapp"

# after
[systemd.myapp]
exec_start = "/usr/bin/myapp"
[systemd.myapp.timer]
on_boot_sec = 60
Defensive patterns

Strategy: validation

Validate before calling

const SERVICE_ONLY: &[&str] = &["exec_start", "type", "remain_after_exit", "environment", "standard_output", "standard_error"];
const TIMER_KEYS: &[&str] = &["on_boot_sec", "on_unit_active_sec", "on_unit_inactive_sec", "on_calendar", "randomized_delay_sec"];
fn check_block(cfg: &toml::Value) -> Result<(), String> {
    let is_timer = TIMER_KEYS.iter().any(|k| cfg.get(k).is_some());
    if is_timer {
        let offenders: Vec<_> = SERVICE_ONLY.iter().filter(|k| cfg.get(*k).is_some()).collect();
        if !offenders.is_empty() { return Err(format!("timer block sets service-only keys: {offenders:?}")); }
    }
    Ok(())
}

Prevention

When it happens

Trigger: Declaring `[systemd.<name>]` with a schedule key such as `on_calendar` or `on_boot_sec` AND any of the service-only keys (`exec_start`, `type`, `remain_after_exit`, `environment`, `standard_output`, `standard_error`, ...) in the same block.

Common situations: Merging a service unit and a timer unit into one config block instead of two; converting an existing service to a timer by just adding `on_boot_sec` without removing service fields.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/eff1e19779ed0424. Report an issue: GitHub.