jdx/mise · warning

app Info.plist must be a regular file

Error message

app Info.plist must be a regular file

What it means

When determining an installed cask app's version, mise opens the app bundle's Info.plist with O_NOFOLLOW (symlinks rejected) and then verifies via fstat that it is a regular file. Anything else (directory, symlink, fifo, device) triggers this bail to avoid unsafe plist reads.

Solutions

  1. Inspect the app: ls -la '/Applications/MyApp.app/Contents/Info.plist'
  2. Restore the app bundle — reinstall the cask (mise should surface an installed_skip_reason otherwise)
  3. Replace the symlink with a real plist file if intentional
  4. Delete broken bundles that mise should no longer treat as installed

Example fix

// before
$ ls -l MyApp.app/Contents/Info.plist
Info.plist -> /etc/passwd   # symlink
// after
$ rm MyApp.app/Contents/Info.plist && reinstall the app
Defensive patterns

Strategy: try-catch

Validate before calling

const st = fs.lstatSync(appPath + '/Contents/Info.plist');
if (!st.isFile()) console.warn('Info.plist is not a regular file; reinstall the cask');

Type guard

function isRegularPlist(appPath: string): boolean {
  try { return fs.lstatSync(appPath + '/Contents/Info.plist').isFile(); }
  catch { return false; }
}

Try / catch

match read_app_version(app) {
    Err(e) if e.to_string().contains("must be a regular file") => reinstall_cask(app),
    other => other?,
}

Prevention

When it happens

Trigger: The Info.plist inside an installed .app bundle is a symlink, directory, or special file rather than a regular file; the open succeeds (or the stat shows the wrong type) and the S_IFREG check fails.

Common situations: Tampered or corrupted app bundles; sync tools (Dropbox/iCloud) replacing files with placeholders/symlinks; malicious bundles where plist is a symlink to elsewhere — a security guard.

Related errors


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

Appendix: source

Thrown at src/system/packages/brew/cask/app_version.rs:60

/// Reads optional short and build strings from an absolute app bundle path.
///
/// Accepts XML and binary plists. Returns an error for unsafe paths, nonregular
/// files, malformed dictionaries, or version fields whose values are not strings.
pub(super) fn read_app_version(app: &Path) -> Result<AppVersion> {
    use nix::fcntl::{OFlag, openat};
    use nix::sys::stat::{Mode, SFlag, fstat};

    let contents = app.join("Contents");
    let parent = open_trusted_directory(Path::new("/"), contents.strip_prefix("/")?, true, false)?;
    let fd = openat(
        &parent.fd,
        "Info.plist",
        OFlag::O_RDONLY | OFlag::O_NOFOLLOW | OFlag::O_NONBLOCK,
        Mode::empty(),
    )?;
    if SFlag::from_bits_truncate(fstat(&fd)?.st_mode) & SFlag::S_IFMT != SFlag::S_IFREG {
        bail!("app Info.plist must be a regular file");
    }
    let plist = plist::Value::from_reader(std::fs::File::from(fd))?;
    let dict = plist
        .as_dictionary()
        .ok_or_else(|| eyre!("app Info.plist must be a dictionary"))?;
    let field = |key| -> Result<Option<String>> {
        dict.get(key)
            .map(|value| {
                value
                    .as_string()
                    .map(str::to_owned)
                    .ok_or_else(|| eyre!("app {key} must be a string"))
            })
            .transpose()
    };
    Ok(AppVersion {
        short: field("CFBundleShortVersionString")?,
        build: field("CFBundleVersion")?,

View on GitHub (pinned to 533346cc37)