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
- Inspect the app: ls -la '/Applications/MyApp.app/Contents/Info.plist'
- Restore the app bundle — reinstall the cask (mise should surface an installed_skip_reason otherwise)
- Replace the symlink with a real plist file if intentional
- 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
- Keep app bundles out of cloud-sync placeholder directories
- Reinstall casks whose bundles were modified by other tools
- Treat non-regular Info.plist as a tampering signal and investigate
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
- app target is outside an allowed Applications directory
- binary target is not an owned Caskroom symlink
- brew-cask: invalid app source
- brew-cask: refusing generic artifact source outside the…
- brew-cask: refusing to stage cask payload through a path…
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)