nikivdev/code · error

Failed to bootstrap service: {}

Error message

Failed to bootstrap service: {}

What it means

This is the second bootstrap attempt in enable_service (loading the service), which unlike error 684 tolerates 'already loaded' stderr but still fails when launchctl exits non-zero for any other reason. The distinguishing stderr text is checked before bailing, so genuine bootstrap errors surface here.

Source

Thrown at src/macos.rs:771

    // Then bootstrap (load)
    let mut cmd = if svc.service_type.requires_sudo() {
        let mut c = Command::new("sudo");
        c.args(["launchctl", "bootstrap", &domain]);
        c.arg(&svc.plist_path);
        c
    } else {
        let mut c = Command::new("launchctl");
        c.args(["bootstrap", &domain]);
        c.arg(&svc.plist_path);
        c
    };

    let output = cmd.output()?;
    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        // Bootstrap may fail if already loaded
        if !stderr.contains("already loaded") && !stderr.contains("service already loaded") {
            bail!("Failed to bootstrap service: {}", stderr);
        }
    }

    Ok(())
}

/// Get the current user's UID.
fn get_uid() -> u32 {
    unsafe { libc::getuid() }
}

/// Load macOS config from global flow.toml.
fn load_macos_config() -> Option<MacosConfig> {
    let config_path = config::default_config_path();
    if !config_path.exists() {
        return None;
    }

View on GitHub (pinned to a747e741ae)

Solutions

  1. Read the embedded stderr for launchctl's specific failure reason.
  2. If the service actually is already loaded, verify with `launchctl print <domain>/<label>` and treat the enable as a no-op success.
  3. Run with sudo / in a GUI session as appropriate for the service's domain.
  4. Check the plist is readable by the invoking user (permissions/ownership).

Example fix

// before
// error: "Failed to bootstrap service: Bootstrap failed: 125: Domain operation not supported"
ssh host flow macos enable my-daemon
// after
// run in a GUI session, or target the system domain explicitly
sudo flow macos enable my-daemon
Defensive patterns

Strategy: try-catch

Validate before calling

let already = std::process::Command::new("launchctl")
    .args(["print", &format!("gui/{}/{}", uid, label)])
    .status()
    .map(|s| s.success())
    .unwrap_or(false);
if already { eprintln!("service already loaded; skipping enable"); return; }

Type guard

fn is_non_idempotent_bootstrap_failure(err: &anyhow::Error) -> bool {
    let m = err.to_string();
    m.contains("Failed to bootstrap service:") && !m.contains("already loaded")
}

Try / catch

match macos::run_enable(opts) {
    Err(e) if e.to_string().contains("Failed to bootstrap service:") => {
        // "already loaded" cases are tolerated internally; anything reaching here is real
        eprintln!("bootstrap failed: {}", e);
    }
    other => other?,
}

Prevention

When it happens

Trigger: launchctl bootstrap fails with an error whose stderr does NOT contain "already loaded" or "service already loaded" — e.g. permission denied on the plist, invalid domain, or bootstrap for a GUI service without a logged-in session.

Common situations: Enabling a service that was loaded under a different domain, so bootstrap fails with an unexpected message; running from an SSH session where the GUI domain is unavailable.

Related errors


AI-assisted analysis of nikivdev/code@a747e741ae (2026-09-01). Data as JSON: /api/errors/b262abe98bf06b88. Report an issue: GitHub.