{"record":{"id":"76f0a4f2801489bc","repo":"jdx/mise","slug":"agent-name-name-must-contain-only-letters-num","errorCode":null,"errorMessage":"agent name '{name}' must contain only letters, numbers, '.', '_', or '-'","messagePattern":"agent name '(.+?)' must contain only letters, numbers, '\\.', '_', or '-'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/launchd.rs","lineNumber":104,"sourceCode":"pub enum LaunchdState {\n    Loaded,\n    Unloaded,\n    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)","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/launchd.rs#L86-L122","documentation":"LaunchdRequest::from_toml validates the agent name — the TOML key under [bootstrap.macos.launchd.agents]. valid_name requires it to be non-empty and contain only ASCII letters, digits, '.', '_', '-'. The name becomes part of the launchd label dev.mise.<name>, so characters that cannot round-trip through launchd/plist handling are rejected up front. Note the aggregation layer logs this as a warning and skips the agent.","triggerScenarios":"Keying an agent as \"sync worker\" (space), \"backup/nightly\" (slash), a non-ASCII name, or an empty key inside [bootstrap.macos.launchd.agents].","commonSituations":"Human-readable labels with spaces used as agent names; copying systemd unit names that contain '@' or '.'-adjacent separators; localized agent names; names pasted from URLs or paths.","solutions":["Rename the agent key to use only alphanumerics plus '.', '_', '-'","Keep human descriptions out of the key; put them in a comment or docs","Re-run mise bootstrap and verify the agent loads with label dev.mise.<name>"],"exampleFix":"# before\n[bootstrap.macos.launchd.agents.\"sync worker\"]\nprogram = \"/usr/local/bin/sync\"\n\n# after\n[bootstrap.macos.launchd.agents.sync-worker]\nprogram = \"/usr/local/bin/sync\"","handlingStrategy":"validation","validationCode":"python3 - <<'EOF'\nimport tomllib,sys,re\nc=tomllib.load(open('mise.toml','rb'))\nfor name in (c.get('bootstrap',{}).get('macos',{}).get('launchd',{}).get('agents',{}) or {}):\n    if not re.fullmatch(r'[A-Za-z0-9._-]+',name): sys.exit(f'invalid launchd agent name: {name!r}')\nEOF","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use slug-style agent names (letters, digits, dot, underscore, hyphen)","Keep labels out of keys; launchd labels get the dev.mise. prefix automatically","Re-run mise bootstrap after renaming and check the warning log for skipped agents"],"tags":["mise","macos","launchd","bootstrap","config-validation"],"backgroundTag":"config-validation-failed","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}