espanso/espanso · error

Unable to spawn worker monitor thread

Error message

Unable to spawn worker monitor thread

What it means

After spawning the worker child, spawn_worker starts a 'worker-status-monitor' thread via std::thread::Builder::new().spawn to watch the child and forward its exit code. If the thread cannot be created, the expect panics with this message, leaving the worker unmonitored.

Source

Thrown at espanso/src/cli/daemon/mod.rs:325

    std::thread::Builder::new()
        .name("worker-status-monitor".to_string())
        .spawn(move || {
            let result = child.wait();
            if let Ok(status) = result {
                if let Some(code) = status.code() {
                    if code != WORKER_SUCCESS {
                        exit_notify
                            .send(code)
                            .expect("unable to forward worker exit code");
                    }
                } else {
                    exit_notify
                        .send(WORKER_ERROR_EXIT_NO_CODE)
                        .expect("unable to forward worker exit code");
                }
            }
        })
        .expect("Unable to spawn worker monitor thread");
}

fn restart_worker(
    paths: &Paths,
    paths_overrides: &PathsOverrides,
    exit_notify: Sender<i32>,
    start_reason: Option<String>,
) {
    match create_ipc_client_to_worker(&paths.runtime) {
        Ok(mut worker_ipc) => {
            if let Err(err) = worker_ipc.send_async(IPCEvent::Exit) {
                error!("unable to send termination signal to worker process: {err}");
            }
        }
        Err(err) => {
            error!("could not establish IPC connection with worker: {err}");
        }
    }

View on GitHub (pinned to e6c3736675)

Solutions

  1. Check and raise thread/process limits (ulimit -u, container pids limit)
  2. Free memory or reduce concurrent threads in the environment
  3. Confirm the process is not leaking threads that exhausted the limit
  4. Convert the expect into error handling that retries thread creation or exits gracefully

Example fix

// before
.expect("Unable to spawn worker monitor thread");
// after
if let Err(err) = std::thread::Builder::new()
    .name("worker-status-monitor".to_string())
    .spawn(move || { /* monitor loop */ })
{
    error!("Unable to spawn worker monitor thread: {err}");
}
Defensive patterns

Strategy: try-catch

Try / catch

match std::thread::Builder::new().name("worker-status-monitor".to_string()).spawn(move || { /* ... */ }) {
    Ok(handle) => { /* keep handle */ },
    Err(err) => { error!("monitor thread spawn failed: {err}"); }
}

Prevention

When it happens

Trigger: std::thread::Builder::spawn returns Err, i.e. the OS refuses to allocate a new thread — thread-count limits (RLIMIT_NPROC / cgroup pids), out of memory for the new stack, or a security policy blocking thread creation.

Common situations: Systems at their max thread/process limit, containers with low pids.cgroup limits, severe memory exhaustion, hardened sandboxes restricting clone().

Related errors


AI-assisted analysis of espanso/espanso@e6c3736675 (2026-09-06). Data as JSON: /api/errors/6059fefd741f2a83. Report an issue: GitHub.