jdx/mise · error

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

Error message

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

What it means

A launchd agent's program value is trimmed before validation; if what remains is empty (program = "" or only whitespace), LaunchdRequest::from_toml rejects it with this error and the agent is skipped. This is distinct from the missing-key error: the key is present but carries no usable executable path.

Source

Thrown at src/system/launchd.rs:110

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

impl LaunchdRequest {
    pub 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 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)
            if !is_absolute_launchd_path(dir) {
                bail!(
                    "agent '{name}' `queue_directories` entry '{dir}' must be an absolute path \
                     (launchd requires absolute paths; `~` and `~/` are expanded)"
                );
            }

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Set program to a real absolute path, e.g. program = "/usr/local/bin/sync"
  2. If the agent is intentionally disabled, remove the whole agent table instead of blanking program
  3. Check templated configs render a non-empty path (mise exec -- templating dry-run or render the file once)

Example fix

# before
[bootstrap.macos.launchd.agents.sync]
program = ""

# after
[bootstrap.macos.launchd.agents.sync]
program = "/usr/local/bin/sync"
Defensive patterns

Strategy: validation

Validate before calling

python3 - <<'EOF'
import tomllib,sys
c=tomllib.load(open('mise.toml','rb'))
for name,agent in (c.get('bootstrap',{}).get('macos',{}).get('launchd',{}).get('agents',{}) or {}).items():
    p=agent.get('program')
    if isinstance(p,str) and not p.strip(): sys.exit(f'agent {name} has empty program')
EOF

Prevention

When it happens

Trigger: Writing program = "" or program = " " in [bootstrap.macos.launchd.agents.<name>], or a templating placeholder that renders to whitespace.

Common situations: Placeholder values left from scaffolding; templates where the program variable is undefined and expands to empty; accidental whitespace from copy-paste or trailing newlines in generated TOML.

Understand the failure class

Background: Config validation failed: what "invalid value for {key}" and settings-rejection errors mean across 19 open-source libraries — this error's family across 19 libraries.

Related errors


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