jdx/mise · error

unit name ' ' must contain only letters, numbers, '.', '_'…

Error message

unit name '{name}' must contain only letters, numbers, '.', '_', '-', or '@'

What it means

SystemdRequest::from_toml validates unit names from mise.toml systemd config against valid_name(), which permits only letters, numbers, '.', '_', '-', and '@'. A name containing any other character (spaces, slashes, colons, etc.) is rejected before a unit file is generated, preventing malformed or unsafe systemd unit names.

Solutions

  1. Rename the unit to only use letters, numbers, '.', '_', '-', '@' (e.g. 'my-worker.service')
  2. Move descriptive text into config fields like description/comment, not the unit name
  3. Check for accidental whitespace or trailing characters in the TOML key

Example fix

# before (mise.toml)
[systemd."my app.service"]
exec_start = "/usr/bin/myapp"
# after
[systemd."my-app.service"]
exec_start = "/usr/bin/myapp"
Defensive patterns

Strategy: validation

Validate before calling

fn valid_unit_name(name: &str) -> bool {
    !name.is_empty()
        && name.chars().all(|c| c.is_ascii_alphanumeric() || "._-@".contains(c))
}
// check before writing the TOML entry

Prevention

When it happens

Trigger: Defining a systemd unit in mise.toml ([systemd] or equivalent table) whose key/name contains characters outside [A-Za-z0-9._@-], then running the command that loads systemd requests via from_toml.

Common situations: Copy-pasting a unit description with spaces into the name field; using a path-like name such as 'my/app.service'; typos like 'worker:one.service'; shell-interpolated names.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/8d121fa53f67e499. Report an issue: GitHub.

Appendix: source

Thrown at src/system/systemd.rs:159

    pub active: bool,
    pub enabled: bool,
    pub state: SystemdState,
}

impl SystemdStatus {
    pub(crate) fn is_desired(&self) -> bool {
        match self.state {
            SystemdState::Active => self.request.start,
            SystemdState::Inactive => !self.request.start,
            SystemdState::Differs | SystemdState::Missing => false,
        }
    }
}

impl SystemdRequest {
    pub(crate) fn from_toml(name: String, config: SystemdTomlConfig) -> Result<Self> {
        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`");
        }

View on GitHub (pinned to 533346cc37)