jdx/mise · warning

another process is installing the mise notification helper

Error message

another process is installing the mise notification helper

What it means

ensure_app stages and installs the notification helper app into the data directory under an install.lock fslock. When a complete app is not already present and another process holds the lock, it bails out so only one installer runs at a time, preventing two processes from corrupting the staged bundle.

Source

Thrown at src/system/history/notify/macos.rs:71

    let app = ensure_app(&crate::dirs::DATA.join("notifications"))?;
    Ok(notification_command(&app, title, body))
}

fn notification_command(app: &Path, title: &str, body: &str) -> Command {
    let mut command = Command::new(executable(app));
    command.args([title, body]);
    command
}

fn ensure_app(root: &Path) -> Result<PathBuf> {
    let app = app_path(root);
    if complete(&app) {
        return Ok(app);
    }
    crate::file::create_dir_all(root)?;
    let mut lock = fslock::LockFile::open(&root.join("install.lock"))?;
    if !lock.try_lock()? {
        bail!("another process is installing the mise notification helper");
    }
    if complete(&app) {
        return Ok(app);
    }
    let staging = tempfile::tempdir_in(root)?;
    let staged = staging.path().join("mise.app");
    let contents = staged.join("Contents");
    std::fs::create_dir_all(contents.join("MacOS"))?;
    std::fs::create_dir_all(contents.join("Resources"))?;
    std::fs::create_dir_all(contents.join("_CodeSignature"))?;
    std::fs::write(executable(&staged), HELPER)?;
    std::fs::set_permissions(executable(&staged), std::fs::Permissions::from_mode(0o755))?;
    std::fs::write(contents.join("Info.plist"), INFO)?;
    std::fs::write(contents.join("Resources/mise.icns"), ICON)?;
    std::fs::write(
        contents.join("_CodeSignature/CodeResources"),
        CODE_RESOURCES,
    )?;

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Retry after a short delay — once the other process finishes installing, complete() succeeds and the lock path is skipped.
  2. Check for a hung process holding install.lock (lsof on the data dir) and wait or kill it.
  3. If a crashed installer left a partial bundle, remove the incomplete app in the data dir and let the next call repair it.
  4. Serialize notification setup in your scripts (only one mise process should install the helper first).

Example fix

// before: parallel notification calls racing
mise notify & mise notify &
// after: serialize first-time install
mise notify && mise notify  # or wait for the first install to complete
Defensive patterns

Strategy: retry

Validate before calling

let lock_free = !std::path::Path::new(&data_dir).join("notifications/install.lock")
    .metadata().map(|m| m.len() > 0).unwrap_or(false); // advisory only; rely on retry

Try / catch

for attempt in 0..5 {
    match ensure_app(root) {
        Err(e) if e.to_string().contains("another process is installing") => {
            std::thread::sleep(Duration::from_millis(500 * (attempt + 1)));
        }
        other => { other?; break; }
    }
}

Prevention

When it happens

Trigger: Concurrent calls to notification()/ensure_app (e.g. two mise processes emitting notifications) where the app is absent or incomplete and both race to acquire root/install.lock; a stale/crashed process briefly holding the lock.

Common situations: Multiple shells or scripts firing notifications simultaneously on first run; CI or parallel test runs sharing the same data directory; an interrupted first installation leaving an incomplete app so every process takes the lock path.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/f4f31e7c50ee7fab. Report an issue: GitHub.