zeroclaw-labs/zeroclaw · critical · anyhow::Error

launchctl start failed: {}

Error message

launchctl start failed: {}

What it means

Thrown by maybe_restart_managed_daemon_service on macOS when the com.zeroclaw.daemon launchd job is installed (plist exists and appears in `launchctl list`) and an unconditional `launchctl stop` is followed by `launchctl start com.zeroclaw.daemon`, but start exits non-zero. The daemon was already stopped, so this leaves the service down until the failure is addressed; callers print a manual-restart hint when this bubbles up.

Source

Thrown at crates/zeroclaw-channels/src/orchestrator/mod.rs:8771

        let list_output = Command::new("launchctl")
            .arg("list")
            .output()
            .context("Failed to query launchctl list")?;
        let listed = String::from_utf8_lossy(&list_output.stdout);
        if !listed.contains("com.zeroclaw.daemon") {
            return Ok(false);
        }

        let _ = Command::new("launchctl")
            .args(["stop", "com.zeroclaw.daemon"])
            .output();
        let start_output = Command::new("launchctl")
            .args(["start", "com.zeroclaw.daemon"])
            .output()
            .context("Failed to start launchd daemon service")?;
        if !start_output.status.success() {
            let stderr = String::from_utf8_lossy(&start_output.stderr);
            anyhow::bail!("launchctl start failed: {}", stderr.trim());
        }

        return Ok(true);
    }

    if cfg!(target_os = "linux") {
        // OpenRC (system-wide) takes precedence over systemd (user-level)
        let openrc_init_script = PathBuf::from("/etc/init.d/zeroclaw");
        if openrc_init_script.exists()
            && let Ok(status_output) = Command::new("rc-service").args(OPENRC_STATUS_ARGS).output()
        {
            // rc-service exits 0 if running, non-zero otherwise
            if status_output.status.success() {
                let restart_output = Command::new("rc-service")
                    .args(OPENRC_RESTART_ARGS)
                    .output()
                    .context("Failed to restart OpenRC daemon service")?;
                if !restart_output.status.success() {

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Bring the daemon back first: `zeroclaw service stop && zeroclaw service start` (or `launchctl start com.zeroclaw.daemon`) and read the printed stderr — it names the launchd failure
  2. Verify the plist's program path still points at the installed zeroclaw binary; reinstall the service if the binary moved
  3. Validate the daemon starts manually: run the binary in the foreground and fix any config error that crashes it on boot
  4. Check `launchctl list | grep zeroclaw` and system log (`log show --predicate 'process == "launchd"'`) for job-load errors

Example fix

# before: stale plist after reinstall
# launchctl start failed: ... (program path no longer exists)

# after
zeroclaw service stop
zeroclaw service start   # re-installs plist with current binary path
Defensive patterns

Strategy: fallback

Validate before calling

// Verify the daemon job is healthy before the operation that triggers auto-restart:
let listed = Command::new("launchctl").arg("list").output()?;
if !String::from_utf8_lossy(&listed.stdout).contains("com.zeroclaw.daemon") {
    return Ok(()); // not managed by launchd; nothing to break
}

Try / catch

if let Err(e) = maybe_restart_managed_daemon_service() {
    eprintln!("auto-restart failed: {e:#}; run `zeroclaw service stop && zeroclaw service start`");
    // config was already saved — do not roll it back, just surface the manual step
}

Prevention

When it happens

Trigger: Saving a config that triggers an automatic daemon reload (e.g. the allowlist save path around line 8730) on macOS while `launchctl start` fails — common causes: the plist's program path is stale after a reinstall, the binary moved, or launchd rejects the job definition.

Common situations: ZeroClaw binary was upgraded/relocated but ~/Library/LaunchAgents/com.zeroclaw.daemon.plist still points at the old path; plist hand-edited into an invalid state; macOS sandbox/permissions blocking launchctl for the session; daemon crashed at startup due to bad config so start immediately fails.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/c6560d0e049eef83. Report an issue: GitHub.