jdx/mise · error

the embedded notification helper is not Developer ID signed

Error message

the embedded notification helper is not Developer ID signed

What it means

On macOS, mise shows notifications through an embedded helper app bundle that must be signed with a Developer ID (and contain the expected icon/signature resources). notification() refuses to proceed when the checked bundle fails the release_signed() probe, because unsigned helpers are killed by notification infrastructure (especially Gatekeeper/notarization rules).

Solutions

  1. Install an official mise release (Developer ID signed) via the recommended installer or package manager instead of a locally built binary.
  2. Re-build/obtain the helper with a valid Developer ID Application certificate: `codesign --sign "Developer ID Application: <team>"`.
  3. Verify the signature with `codesign -dv --verbose=4 <path>/mise.app` and re-deploy the correct bundle.
  4. Disable notifications from mise if unsigned local builds are required (avoid the notify path).

Example fix

// before (local build)
cargo build --release   # helper ad-hoc signed -> error
// after
# install official signed release
curl https://mise.run | sh
# or sign the helper
codesign --force --sign "Developer ID Application: YOURTEAM" mise.app
Defensive patterns

Strategy: fallback

Validate before calling

const { execSync } = require('child_process');
export function isDeveloperIdSigned(appPath: string): boolean {
  try {
    const out = execSync(`codesign -dv "${appPath}" 2>&1`).toString();
    return /Authority=Developer ID Application/.test(out);
  } catch {
    return false;
  }
}

Type guard

function hasCodeSignature(app: string): boolean {
  return require('fs').existsSync(`${app}/Contents/_CodeSignature/CodeResources`);
}

Try / catch

try {
  await notify(title, body);
} catch (e) {
  if (/not Developer ID signed/.test(String(e))) {
    logger.warn('notifications unavailable for unsigned local builds');
  } else throw e;
}

Prevention

When it happens

Trigger: Requesting a macOS notification when the helper app at Contents/MacOS / mise.icns / _CodeSignature checks pass structurally but the code signature is not a Developer ID signature — e.g. running a self-built or ad-hoc-signed mise, or a tampered helper bundle.

Common situations: Developer built mise from source and its embedded app is ad-hoc signed; the helper bundle was rebuilt locally losing the Developer ID signature; a packaged distribution stripped or re-signed the embedded app with a non-Developer ID identity.

Understand the failure class

Background: "not installed", "pip install", "required for": how missing-dependency errors surface across open-source libraries — this error's family across 34 libraries.

Related errors


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

Appendix: source

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

    // running helper. The bundle identifier remains stable across versions.
    let fingerprint = crate::hash::hash_to_str(&(HELPER, INFO, ICON, CODE_RESOURCES));
    root.join(fingerprint).join("mise.app")
}

fn executable(app: &Path) -> PathBuf {
    app.join("Contents/MacOS/mise-notify")
}

fn complete(app: &Path) -> bool {
    executable(app).is_file()
        && app.join("Contents/Info.plist").is_file()
        && app.join("Contents/Resources/mise.icns").is_file()
        && app.join("Contents/_CodeSignature/CodeResources").is_file()
}

pub(super) fn notification(title: &str, body: &str) -> Result<Command> {
    if !release_signed() {
        bail!("the embedded notification helper is not Developer ID signed");
    }
    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"))?;

View on GitHub (pinned to 533346cc37)