jdx/mise · error

agent '{agent_name}' `start_calendar_interval` must set at l

Error message

agent '{agent_name}' `start_calendar_interval` must set at least one field

What it means

A single-table start_calendar_interval must set at least one of minute, hour, day, weekday, or month — launchd treats unset fields as wildcards, but a table where every field is unset is meaningless. LaunchdCalendarInterval::validate rejects that case, then range-checks each set field (minute 0-59, hour 0-23, day 1-31, weekday 0-7, month 1-12). The whole agent is skipped with a warning.

Source

Thrown at src/system/launchd.rs:158

            queue_directories: config.queue_directories,
            environment: config.environment,
            working_directory: config.working_directory,
            stdout_path: config.stdout_path,
            stderr_path: config.stderr_path,
            kickstart: config.kickstart,
        })
    }
}

impl LaunchdCalendarInterval {
    fn validate(&self, agent_name: &str) -> Result<()> {
        if self.minute.is_none()
            && self.hour.is_none()
            && self.day.is_none()
            && self.weekday.is_none()
            && self.month.is_none()
        {
            bail!("agent '{agent_name}' `start_calendar_interval` must set at least one field");
        }
        validate_range(agent_name, "minute", self.minute, 0, 59)?;
        validate_range(agent_name, "hour", self.hour, 0, 23)?;
        validate_range(agent_name, "day", self.day, 1, 31)?;
        validate_range(agent_name, "weekday", self.weekday, 0, 7)?;
        validate_range(agent_name, "month", self.month, 1, 12)?;
        Ok(())
    }
}

impl LaunchdCalendarIntervals {
    fn validate(&self, agent_name: &str) -> Result<()> {
        match self {
            Self::Single(interval) => interval.validate(agent_name),
            Self::Multiple(intervals) => {
                if intervals.is_empty() {
                    bail!("agent '{agent_name}' `start_calendar_interval` must not be empty");
                }

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Set at least one recognized field (minute/hour/day/weekday/month) with lowercase keys
  2. Use 24-hour hour values and launchd ranges (minute 0-59, hour 0-23, day 1-31, weekday 0-7, month 1-12)
  3. If no schedule is needed, remove start_calendar_interval and rely on run_at_load/keep_alive

Example fix

# before
[bootstrap.macos.launchd.agents.backup.start_calendar_interval]
second = 30

# after
[bootstrap.macos.launchd.agents.backup.start_calendar_interval]
hour = 3
minute = 30
Defensive patterns

Strategy: validation

Validate before calling

python3 - <<'EOF'
import tomllib,sys
c=tomllib.load(open('mise.toml','rb'))
ranges={'minute':(0,59),'hour':(0,23),'day':(1,31),'weekday':(0,7),'month':(1,12)}
for name,agent in (c.get('bootstrap',{}).get('macos',{}).get('launchd',{}).get('agents',{}) or {}).items():
    iv=agent.get('start_calendar_interval')
    ivs=iv if isinstance(iv,list) else [iv] if isinstance(iv,dict) else []
    for i in ivs:
        if not i or not any(k in i for k in ranges): sys.exit(f'agent {name}: interval sets no recognized field')
        for k,v in i.items():
            if k in ranges and not (ranges[k][0]<=v<=ranges[k][1]): sys.exit(f'agent {name}: {k}={v} out of range')
EOF

Prevention

When it happens

Trigger: Writing an empty [bootstrap.macos.launchd.agents.<name>.start_calendar_interval] table, or one containing only unrecognized keys (e.g. second = 30, or capitalized Minute), which deserialize as unset.

Common situations: Placeholder tables meant to be filled in later; expecting cron-string or 'second' syntax like other schedulers; key-name casing mistakes that silently deserialize to defaults.

Understand the failure class

Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.

Related errors


AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17). Data as JSON: /api/errors/11cd101140f57aec. Report an issue: GitHub.