jdx/mise · error

agent ' ' must set a non-empty `program

Error message

agent '{name}' must set a non-empty `program`

What it means

After reading `program`, LaunchdRequest::from_toml trims it and rejects an empty result. A program consisting solely of whitespace would produce an invalid launchd plist executable, so it bails with this message. It complements the missing-`program` check by catching present-but-empty values.

Solutions

  1. Set `program` to the actual executable path or command
  2. Remove the agent entirely if no program is intended
  3. If using templated values, ensure the template renders to a non-empty path

Example fix

# before
[agent.backup]
program = ""
# after
[agent.backup]
program = "/usr/local/bin/backup.sh"
Defensive patterns

Strategy: validation

Validate before calling

prog = agent_cfg.get("program", "")
if not prog.strip():
    raise ValueError(f"agent '{name}' program is empty")

Prevention

When it happens

Trigger: Setting program = "" or program = " " (whitespace only) in an agent config, or passing an empty program string programmatically.

Common situations: Placeholder values left during templating (e.g. program = "${BIN}" where the variable expands empty); clearing a value by blanking it instead of removing the agent; quoting mistakes leaving an empty string.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at src/system/launchd.rs:118

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct LaunchdStatus {
    pub request: LaunchdRequest,
    pub path: PathBuf,
    pub loaded: bool,
    pub state: LaunchdState,
}

impl LaunchdRequest {
    pub(crate) fn from_toml(name: String, config: LaunchdTomlConfig) -> Result<Self> {
        if !valid_name(&name) {
            bail!("agent name '{name}' must contain only letters, numbers, '.', '_', or '-'");
        }
        let Some(program) = config.program.map(|s| s.trim().to_string()) else {
            bail!("agent '{name}' must set `program`");
        };
        if program.is_empty() {
            bail!("agent '{name}' must set a non-empty `program`");
        }
        if config.keep_alive && config.keep_alive_on_failure {
            bail!("agent '{name}' cannot set both `keep_alive` and `keep_alive_on_failure`");
        }
        if config.nice.is_some_and(|nice| !(-20..=20).contains(&nice)) {
            bail!("agent '{name}' `nice` must be between -20 and 20");
        }
        if let Some(interval) = &config.start_calendar_interval {
            interval.validate(&name)?;
        }
        for dir in &config.queue_directories {
            if dir.trim().is_empty() {
                bail!("agent '{name}' `queue_directories` must not contain empty entries");
            }
            // checked against the raw string rather than `Path::is_absolute` on the
            // expanded value: the plist is consumed by macOS launchd, so POSIX rules
            // apply regardless of the platform parsing the config, and on Windows
            // `Path::new("/var/spool").is_absolute()` is false (root, but no prefix)

View on GitHub (pinned to 533346cc37)