jdx/mise · error
service unit '{name}' must set a non-empty `exec_start`
Error message
service unit '{name}' must set a non-empty `exec_start` What it means
A systemd *service* unit must define what to run. `SystemdRequest::from_toml` requires a non-empty `exec_start` (after trimming whitespace) whenever the unit resolves to a Service. Timers are exempt because they reference a companion service instead.
Source
Thrown at src/system/systemd.rs:176
if !valid_name(&name) {
bail!("unit name '{name}' must contain only letters, numbers, '.', '_', '-', or '@'");
}
let is_timer = config.on_boot_sec.is_some()
|| config.on_unit_active_sec.is_some()
|| config.on_unit_inactive_sec.is_some()
|| config.on_calendar.is_some()
|| config.randomized_delay_sec.is_some()
|| config.accuracy_sec.is_some()
|| config.persistent.is_some()
|| config.unit.is_some();
let kind = if is_timer {
SystemdUnitKind::Timer
} else {
SystemdUnitKind::Service
};
let exec_start = config.exec_start.map(|s| s.trim().to_string());
if kind == SystemdUnitKind::Service && exec_start.as_deref().is_none_or(str::is_empty) {
bail!("service unit '{name}' must set a non-empty `exec_start`");
}
if kind == SystemdUnitKind::Timer {
let service_only_fields = [
(exec_start.is_some(), "exec_start"),
(config.service_type.is_some(), "type"),
(config.remain_after_exit.is_some(), "remain_after_exit"),
(config.exec_stop.is_some(), "exec_stop"),
(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"),View on GitHub (pinned to afd2eddd3a)
Solutions
- Add a non-empty `exec_start` value, e.g. `exec_start = "/usr/bin/myapp --serve"`.
- If the unit is meant to be timer-driven, add a timer trigger field (`on_boot_sec`, `on_calendar`, etc.) so it is treated as a Timer and define the schedule there.
- Remove the unit entirely if it was added by mistake (`state = "absent"` semantics apply at the service level, not here).
Example fix
# before [systemd.myapp] # exec_start missing # after [systemd.myapp] exec_start = "/usr/local/bin/myapp --daemon"
Defensive patterns
Strategy: validation
Validate before calling
fn has_exec_start(cfg: &toml::Value) -> bool {
cfg.get("exec_start")
.and_then(|v| v.as_str())
.map(|s| !s.trim().is_empty())
.unwrap_or(false)
} Prevention
- Always pair a service unit block with an `exec_start` line.
- Lint mise.toml systemd blocks in CI before deploy.
- When converting a service to a timer, split into two blocks rather than deleting `exec_start`.
When it happens
Trigger: Declaring `[systemd.<name>]` with no `exec_start` key, or `exec_start = ""` or whitespace-only, on a unit that has no timer trigger fields (so kind == Service).
Common situations: Copy-pasting a timer config and forgetting `exec_start`; commenting out the exec line while debugging; defining a unit intended only to be triggered by an existing timer but configuring it as a plain service.
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
- "{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
- bootstrap service '{name}' cannot be both masked and enabled
- invalid bootstrap service name '{name}': use at most 255 ASC
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/63d2d39efe0d045d.
Report an issue: GitHub.