jdx/mise · error

agent ' ' cannot set both `keep_alive` and…

Error message

agent '{name}' cannot set both `keep_alive` and `keep_alive_on_failure`

What it means

launchd offers two mutually exclusive keep-alive semantics: keep_alive (always keep running) and keep_alive_on_failure (restart only on failure). LaunchdRequest::from_toml bails if both are set to true on the same agent, because the resulting plist KeepAlive value would be ambiguous. Only one strategy can be chosen per agent.

Solutions

  1. Keep only one of keep_alive or keep_alive_on_failure set to true
  2. Use keep_alive = true for unconditional restarts
  3. Use keep_alive_on_failure = true to restart only on non-zero exits

Example fix

# before
[agent.worker]
program = "/usr/bin/worker"
keep_alive = true
keep_alive_on_failure = true
# after
[agent.worker]
program = "/usr/bin/worker"
keep_alive_on_failure = true
Defensive patterns

Strategy: validation

Validate before calling

if agent_cfg.get("keep_alive") and agent_cfg.get("keep_alive_on_failure"):
    raise ValueError(f"agent '{name}': pick one keep_alive strategy")

Prevention

When it happens

Trigger: An agent config with both keep_alive = true and keep_alive_on_failure = true, typically from merging two agent templates or incremental edits.

Common situations: Combining shared config snippets each setting a different keep-alive flag; trying to express 'restart always AND on failure' without realizing they overlap; copy-paste between agents that used different flags.

Related errors


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

Appendix: source

Thrown at src/system/launchd.rs:121

    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)
            if !is_absolute_launchd_path(dir) {
                bail!(
                    "agent '{name}' `queue_directories` entry '{dir}' must be an absolute path \

View on GitHub (pinned to 533346cc37)