jdx/mise · error
app Info.plist must be a regular file
Error message
app Info.plist must be a regular file
What it means
read_app_version opens a cask app bundle's Info.plist with O_NOFOLLOW and then verifies via fstat that the opened file is a regular file (S_IFREG). If it is not — e.g. a symlink, directory, FIFO, or device — the function bails instead of reading it, a hardening measure against symlink-based path tricks and unreadable/odd file types in .app bundles.
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 afd2eddd3a)
Solutions
- Inspect the plist path: ls -l '<App>.app/Contents/Info.plist' and replace it with a real regular file (e.g. restore the app by reinstalling the cask: brew reinstall --cask <token>).
- Remove any symlink: delete the symlink and copy the real Info.plist into place, or reinstall the application that owns the bundle.
- If the app sits on a network/exotic mount, move it to a local APFS/HFS+ volume so Info.plist is a regular file.
- If you intentionally test with odd file types, expect this error — it is a deliberate safety rejection, not a bug.
Example fix
// before (shell): Info.plist is a symlink $ ls -l MyApp.app/Contents/Info.plist Info.plist -> /shared/plists/MyApp.plist // after $ rm MyApp.app/Contents/Info.plist $ cp /shared/plists/MyApp.plist MyApp.app/Contents/Info.plist $ brew reinstall --cask myapp # or let mise re-read the version
Defensive patterns
Strategy: validation
Validate before calling
import std::fs;
use std::os::unix::fs::FileTypeExt;
let meta = std::fs::symlink_metadata(format!("{}.app/Contents/Info.plist", app_path))?;
let ok = !meta.file_type().is_symlink() && meta.is_file();
if !ok { eprintln!("Info.plist must be a regular file (no symlinks): {app_path}"); } Type guard
fn is_regular_plist(path: &std::path::Path) -> bool {
std::fs::metadata(path).map(|m| m.is_file()).unwrap_or(false)
} Try / catch
match read_app_version(&app) {
Ok(v) => println!("version {v}"),
Err(e) if e.to_string().contains("must be a regular file") => {
eprintln!("App bundle is corrupt or tampered; reinstall the cask.");
}
Err(e) => return Err(e),
} Prevention
- Never symlink Info.plist inside .app bundles; keep bundles intact.
- Reinstall casks after manually moving or rsyncing apps between volumes.
- Watch for apps restored from backups with changed file types; verify with ls -l before relying on version reads.
- Treat this error as a tamper/corruption signal rather than trying to bypass the check.
When it happens
Trigger: Calling read_app_version (directly or via installed_skip_reason, or during brew cask install/state checks) on an app whose <App>.app/Contents/Info.plist is not a regular file: a symlinked plist, a directory named Info.plist, a broken/odd filesystem entry, or a maliciously crafted bundle.
Common situations: Corrupted or hand-assembled .app bundles in ~/Applications or /Applications; Homebrew casks whose app target was replaced by a symlink (e.g. pointing Info.plist into a shared resources dir); network mounts or exotic filesystems returning non-regular inode types; tampered bundles flagged during security checks.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- brew-cask: refusing operation through a changed generic arti
- brew-cask: invalid {kind} '{value}'
- brew-cask:{}: cask metadata has no sha256
- brew-cask: staged symlink path escaped extraction root: {}
- brew-cask: refusing generic artifact source outside the extr
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/a712a9df36caa261.
Report an issue: GitHub.