{"record":{"id":"5efa25a78d86e0d4","repo":"jdx/mise","slug":"launchctl-failed","errorCode":null,"errorMessage":"`launchctl {}` failed: {}","messagePattern":"`launchctl (.+?)` failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/launchd.rs","lineNumber":513,"sourceCode":"        .stdin(Stdio::null())\n        .stdout(Stdio::null())\n        .stderr(Stdio::null())\n        .status()\n        .await?;\n    Ok(output.success())\n}\n\nasync fn launchctl(args: &[String]) -> Result<()> {\n    debug!(\"$ launchctl {}\", shell_words::join(args));\n    let output = tokio::process::Command::new(\"launchctl\")\n        .args(args)\n        .stdin(Stdio::null())\n        .stdout(Stdio::piped())\n        .stderr(Stdio::piped())\n        .output()\n        .await?;\n    if !output.status.success() {\n        bail!(\n            \"`launchctl {}` failed: {}\",\n            shell_words::join(args),\n            String::from_utf8_lossy(&output.stderr).trim()\n        );\n    }\n    Ok(())\n}\n\nasync fn bootout(domain: &str, path: &Path) -> Result<()> {\n    let args = [\n        \"bootout\".to_string(),\n        domain.to_string(),\n        path.to_string_lossy().to_string(),\n    ];\n    match launchctl(&args).await {\n        Ok(()) => Ok(()),\n        Err(err) if bootout_missing_error(&err.to_string()) => Ok(()),\n        Err(err) => Err(err),","sourceCodeStart":495,"sourceCodeEnd":531,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/launchd.rs#L495-L531","documentation":"A `launchctl ...` subprocess spawned by mise's launchd backend (bootstrap, bootout, kickstart, enable/disable, etc.) exited with a non-zero status. The message embeds the exact joined argv and launchctl's trimmed stderr, so the launchd error text (e.g. 'Load failed: 5: Input/output error' or 'No such process') is the real diagnostic. Note the async `?` on `.output()` maps spawn/IO failures to a different error; this bail is specifically for a completed process with a failing exit status.","triggerScenarios":"Running `mise bootstrap apply` (or service status/sync flows) where mise executes e.g. `launchctl bootstrap gui/501 /Library/LaunchDaemons/dev.mise.x.plist` or `launchctl bootout gui/501 ...` and launchctl rejects it: malformed/generated plist, label mismatch, operation not permitted (daemon vs gui domain), service not currently loaded when kicking out, or SIP-protected locations.","commonSituations":"Editing a managed plist by hand so the on-disk copy diverges from what launchd loaded; targeting the wrong domain type (gui/<uid> vs system) for a daemon; running without sudo where LaunchDaemons require root; macOS changing bootstrap semantics across versions; a BootOut on an already-booted-out service on newer macOS which returns an error.","solutions":["Read the embedded launchctl stderr — it names the actual OS-level failure; fix that first","Verify the domain and path: user agents need `gui/$(id -u)`, system daemons need `sudo` and the `system` domain","Check the generated plist at ~/Library/LaunchAgents/dev.mise.<name>.plist (or /Library/LaunchDaemons) with `plutil -lint` and compare against the loaded version via `launchctl print`","If the service is in a wedged state, run `launchctl bootout <domain> <plist-or-label>` manually once, then re-run mise","Re-run with MISE_DEBUG=1 to see the exact `$ launchctl ...` line being executed"],"exampleFix":"# before: wrong domain for a LaunchAgent, launchctl exits non-zero\nlaunchctl bootstrap system ~/Library/LaunchAgents/dev.mise.backup.plist\n\n# after: bootstrap into the user's GUI domain\nlaunchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/dev.mise.backup.plist","handlingStrategy":"retry","validationCode":"# Pre-flight the operations mise will perform\nlaunchctl print \"gui/$(id -u)/dev.mise.<name>\" >/dev/null 2>&1 || echo \"agent not loaded yet\"\nplutil -lint ~/Library/LaunchAgents/dev.mise.<name>.plist  # plist is valid","typeGuard":null,"tryCatchPattern":"// Rust callers wrapping mise's system module: distinguish process failure from spawn failure\nmatch launchctl(&args).await {\n    Ok(()) => {}\n    Err(report) if report.to_string().starts_with(\"`launchctl\") => {\n        // completed with non-zero exit: stderr is embedded — surface it, consider one retry after bootout\n        eprintln!(\"launchctl rejected: {report}\");\n    }\n    Err(e) => { /* spawn/IO failure — different remediation */ }\n}","preventionTips":["Keep managed plists untouched; edit mise.toml and let mise regenerate them","Use the right domain: gui/$(id -u) for agents, sudo + system domain for daemons","When a service wedges, `launchctl bootout` manually once before re-running mise","Run with MISE_DEBUG=1 to capture the exact failing launchctl invocation"],"tags":["launchd","launchctl","macos","subprocess","bootstrap"],"backgroundTag":"command-execution-failed","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}