zeroclaw-labs/zeroclaw · error · anyhow::Error
daemon child exited with status {status}
Error message
daemon child exited with status {status} What it means
On macOS the daemon is spawned as a child process under launchd capture (run_with_launchd_capture -> supervise_launchd_child). After stdout/stderr pipes are drained, the supervisor waits for the child and checks its exit status; a non-zero exit raises this error. It means the zeroclaw daemon process itself terminated abnormally while being supervised, not that launchd failed.
Source
Thrown at crates/zeroclaw-runtime/src/service/mod.rs:407
let stderr = child
.stderr
.take()
.context("daemon stderr pipe unavailable")?;
let stdout_sink = writers.stdout.clone();
let stderr_sink = writers.stderr.clone();
let stdout_task = zeroclaw_spawn::spawn!(drain_launchd_pipe(stdout, stdout_sink));
let stderr_task = zeroclaw_spawn::spawn!(drain_launchd_pipe(stderr, stderr_sink));
#[cfg(any(target_os = "macos", all(test, unix)))]
let outcome = wait_for_launchd_child(&mut child, &mut signals).await;
#[cfg(all(test, not(unix)))]
let outcome = wait_for_launchd_child(&mut child).await;
finish_launchd_pipes(stdout_task, stderr_task).await;
let status = outcome?;
if status.success() {
Ok(())
} else {
bail!("daemon child exited with status {status}")
}
}
#[cfg(any(target_os = "macos", test))]
async fn finish_launchd_pipes(
mut stdout: tokio::task::JoinHandle<()>,
mut stderr: tokio::task::JoinHandle<()>,
) {
if tokio::time::timeout(LAUNCHD_PIPE_DRAIN_TIMEOUT, async {
let _ = tokio::join!(&mut stdout, &mut stderr);
})
.await
.is_err()
{
stdout.abort();
stderr.abort();
let _ = tokio::join!(stdout, stderr);
}View on GitHub (pinned to 88bb9c8533)
Solutions
- Run the daemon in the foreground first (e.g. 'zeroclaw run') to see the real startup error and fix it
- Check 'zeroclaw service logs' (or the launchd StandardOutPath/StandardErrorPath files) for the crash reason
- Validate the config file and required keys, then retry 'zeroclaw service start'
- Reinstall with 'zeroclaw service install' if the plist points at a moved or deleted binary
Example fix
// before: start the service blind service::start(&config, InitSystem::Auto)?; // after: smoke-test the daemon in the foreground, then start it // $ zeroclaw run (fix whatever error it prints, then) service::start(&config, InitSystem::Auto)?;
Defensive patterns
Strategy: try-catch
Validate before calling
// preflight: confirm the daemon binary itself launches before supervising it
let probe = Command::new(std::env::current_exe()?).arg("--version").status()?;
if !probe.success() {
// do not attempt service start; surface the environment problem first
} Try / catch
match service::start(&config, InitSystem::Auto) {
Ok(()) => Ok(()),
Err(err) if err.to_string().starts_with("daemon child exited with status") => {
// daemon crashed at startup: surface the logs instead of a raw status code
let _ = service::logs(&config, InitSystem::Auto, 100, false);
Err(err)
}
Err(err) => Err(err),
} Prevention
- Run the daemon in the foreground once after every config or version change before starting it as a service
- Keep launchd StandardOutPath/StandardErrorPath on a writable directory so crash reasons are always captured
- Pin the daemon binary path in the plist to a stable install location
When it happens
Trigger: Calling the macOS service start path that ends in supervise_launchd_child when the daemon binary exits non-zero: invalid zeroclaw.toml, missing provider API key, port already bound, or an immediate crash on startup.
Common situations: Fresh install with a broken config file; upgrade to a version with breaking config changes; API credentials missing from the environment the launchd agent runs in; stale binary path in the plist after an upgrade.
Related errors
- launchctl start failed: {}
- the launchd daemon runner is only supported on macOS
- No log files found in {}. Is the service installed?
- Invalid iMessage target: must be a phone number (+1234567890
- iMessage send failed: {stderr}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/7364f826374f5261.
Report an issue: GitHub.