jdx/mise · error
agent '{name}' `queue_directories` entry '{dir}' must be an
Error message
agent '{name}' `queue_directories` entry '{dir}' must be an absolute path (launchd requires absolute paths; `~` and `~/` are expanded) What it means
launchd demands absolute paths, so every queue_directories entry is checked by is_absolute_launchd_path: it must be exactly "~", start with "~/", or start with '/'. Relative paths ("spool/mail") and tilde-username paths ("~user/spool") are rejected — the latter deliberately, because mise only expands a leading "~/" and would otherwise hand launchd a literal '~user' path. Checked on the raw string so POSIX rules apply even when parsing on Windows.
Source
Thrown at src/system/launchd.rs:124
let Some(program) = config.program.map(|s| s.trim().to_string()) else {
bail!("agent '{name}' must set `program`");
};
if program.is_empty() {
bail!("agent '{name}' must set a non-empty `program`");
}
if let Some(interval) = &config.start_calendar_interval {
interval.validate(&name)?;
}
for dir in &config.queue_directories {
if dir.trim().is_empty() {
bail!("agent '{name}' `queue_directories` must not contain empty entries");
}
// checked against the raw string rather than `Path::is_absolute` on the
// expanded value: the plist is consumed by macOS launchd, so POSIX rules
// apply regardless of the platform parsing the config, and on Windows
// `Path::new("/var/spool").is_absolute()` is false (root, but no prefix)
if !is_absolute_launchd_path(dir) {
bail!(
"agent '{name}' `queue_directories` entry '{dir}' must be an absolute path \
(launchd requires absolute paths; `~` and `~/` are expanded)"
);
}
}
Ok(Self {
label: format!("dev.mise.{name}"),
name,
program,
args: config.args,
run_at_load: config.run_at_load,
keep_alive: config.keep_alive,
start_interval: config.start_interval,
throttle_interval: config.throttle_interval,
start_calendar_interval: config.start_calendar_interval,
queue_directories: config.queue_directories,
environment: config.environment,
working_directory: config.working_directory,View on GitHub (pinned to 9dcfcaa0dc)
Solutions
- Prefix the path with '/' (e.g. /var/spool/mail) or use '~/' for the home directory
- Do not use '~user/' forms; expand the target user's home to an absolute path manually
- Re-run mise bootstrap and confirm the agent validates
Example fix
# before [bootstrap.macos.launchd.agents.mailwatch] queue_directories = ["spool/mail"] # after [bootstrap.macos.launchd.agents.mailwatch] queue_directories = ["/var/spool/mail"]
Defensive patterns
Strategy: validation
Validate before calling
python3 - <<'EOF'
import tomllib,sys
c=tomllib.load(open('mise.toml','rb'))
def ok(p): return p=='~' or p.startswith('~/') or p.startswith('/')
for name,agent in (c.get('bootstrap',{}).get('macos',{}).get('launchd',{}).get('agents',{}) or {}).items():
for d in agent.get('queue_directories',[]) or []:
if not ok(d): sys.exit(f'agent {name}: queue path {d!r} must be absolute, ~, or ~/...')
EOF Prevention
- Always write absolute paths (or ~/) for launchd-managed directories
- Do not use ~user forms — mise does not expand tilde-usernames here
- Author macOS agent config on macOS and verify with launchctl after bootstrap
When it happens
Trigger: Writing queue_directories = ["spool/mail"] (relative), ["~user/Mail"] (tilde-username), or ["./queue"] in a [bootstrap.macos.launchd.agents.<name>] table.
Common situations: Paths written relative to the config file out of habit; '~' expansion assumptions carried from shell usage; configs authored on non-mac systems where the path shape was never exercised by launchd.
Related errors
- agent name '{name}' must contain only letters, numbers, '.',
- agent '{name}' must set `program`
- agent '{name}' must set a non-empty `program`
- agent '{name}' `queue_directories` must not contain empty en
- --localized-dir {raw:?} cannot be carried to Windows: {bad:?
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/a6d8dd21a0d30c9d.
Report an issue: GitHub.