{"record":{"id":"d732f72354b2da83","repo":"jdx/mise","slug":"agent-name-must-set-program","errorCode":null,"errorMessage":"agent '{name}' must set `program`","messagePattern":"agent '(.+?)' must set `program`","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/launchd.rs","lineNumber":107,"sourceCode":"    Differs,\n    Missing,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct LaunchdStatus {\n    pub request: LaunchdRequest,\n    pub path: PathBuf,\n    pub loaded: bool,\n    pub state: LaunchdState,\n}\n\nimpl LaunchdRequest {\n    pub fn from_toml(name: String, config: LaunchdTomlConfig) -> Result<Self> {\n        if !valid_name(&name) {\n            bail!(\"agent name '{name}' must contain only letters, numbers, '.', '_', or '-'\");\n        }\n        let Some(program) = config.program.map(|s| s.trim().to_string()) else {\n            bail!(\"agent '{name}' must set `program`\");\n        };\n        if program.is_empty() {\n            bail!(\"agent '{name}' must set a non-empty `program`\");\n        }\n        if let Some(interval) = &config.start_calendar_interval {\n            interval.validate(&name)?;\n        }\n        for dir in &config.queue_directories {\n            if dir.trim().is_empty() {\n                bail!(\"agent '{name}' `queue_directories` must not contain empty entries\");\n            }\n            // checked against the raw string rather than `Path::is_absolute` on the\n            // expanded value: the plist is consumed by macOS launchd, so POSIX rules\n            // apply regardless of the platform parsing the config, and on Windows\n            // `Path::new(\"/var/spool\").is_absolute()` is false (root, but no prefix)\n            if !is_absolute_launchd_path(dir) {\n                bail!(\n                    \"agent '{name}' `queue_directories` entry '{dir}' must be an absolute path \\","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/launchd.rs#L89-L125","documentation":"LaunchdRequest::from_toml requires every agent in [bootstrap.macos.launchd.agents] to declare a program key — the executable launchd will run. This error fires when the agent table exists without a program key at all; the value is trimmed before checks, and a present-but-empty value is a separate error. The agent is then skipped (logged as a warning).","triggerScenarios":"Writing an agent table that defines args/keep_alive/queue_directories but omits program, e.g. [bootstrap.macos.launchd.agents.worker] with only run_at_load = true.","commonSituations":"Assuming launchd infers the program from the agent name or an args list; incremental edits where program is commented out; YAML-to-TOML migration dropping the key.","solutions":["Add program = \"/absolute/path/to/binary\" to the agent table","Check the key is spelled exactly `program` (not cmd/exec/command)","Re-run mise bootstrap and confirm the agent appears in the plan"],"exampleFix":"# before\n[bootstrap.macos.launchd.agents.sync]\nargs = [\"--daemon\"]\n\n# after\n[bootstrap.macos.launchd.agents.sync]\nprogram = \"/usr/local/bin/sync\"\nargs = [\"--daemon\"]","handlingStrategy":"validation","validationCode":"python3 - <<'EOF'\nimport tomllib,sys\nc=tomllib.load(open('mise.toml','rb'))\nfor name,agent in (c.get('bootstrap',{}).get('macos',{}).get('launchd',{}).get('agents',{}) or {}).items():\n    if not isinstance(agent,dict) or 'program' not in agent: sys.exit(f'agent {name} missing program')\nEOF","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Start every agent definition with program; add other keys afterwards","Use the exact key `program`, not cmd/exec/command","Watch bootstrap output warnings — invalid agents are skipped, not failed, so they can slip through silently"],"tags":["mise","macos","launchd","bootstrap","missing-key"],"backgroundTag":"missing-config-key","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}