elkowar/eww · error
Failed to start command-execution-thread
Error message
Failed to start command-execution-thread
What it means
run_command spawns a thread per executed widget command; the expect panics if std::thread::Builder::spawn fails. Like all spawn failures this is an OS resource error (thread limit, memory), not a problem with the command string itself.
Solutions
- Raise limits: `ulimit -u`, systemd TasksMax, container --pids-limit.
- Restart the eww daemon to clear leaked threads, then investigate why threads accumulated.
- Avoid spawning per command: reuse a worker/thread-pool or an async runtime for widget commands.
- Propagate/log the io::Error instead of panicking so a failed command click doesn't kill the whole daemon.
Example fix
// before
std::thread::Builder::new().name(...).spawn(move || { ... }).expect("Failed to start command-execution-thread");
// after
if let Err(e) = std::thread::Builder::new().name("command-execution".to_string()).spawn(move || { ... }) {
log::error!("Failed to start command-execution-thread: {}", e);
} Defensive patterns
Strategy: try-catch
Validate before calling
let ok = rlimit::getrlimit(rlimit::Resource::NPROC).map(|l| l.0 > 32).unwrap_or(true);
if !ok { log::warn!("thread limit low; widget commands may fail to spawn"); } Try / catch
if let Err(e) = std::thread::Builder::new().name("cmd-exec".into()).spawn(work) {
log::error!("Failed to start command-execution-thread: {}", e);
} Prevention
- Don't spawn a fresh thread per click; reuse a small worker pool
- Raise ulimit -u / TasksMax on desktop sessions
- Restart long-running daemons that accumulate threads
- Log spawn failures instead of panicking the whole UI
When it happens
Trigger: Clicking a widget button (or any command-triggering widget) while the process is at its thread limit: RLIMIT_NPROC exhausted, pids cgroup cap, or out-of-memory at pthread_create.
Common situations: Configs with many frequently-clicked widgets leaking threads over a long session, containers with tiny pids limits, or heavily loaded shared machines.
Related errors
- Failed to start outer-main-async-runtime thread
- Failed to initialize tokio runtime
- Failed to initialize tokio runtime
AI-assisted analysis of elkowar/eww@48f5aa8b37 (2026-09-08).
Data as JSON: /api/errors/60cf5503210dbb12.
Report an issue: GitHub.
Appendix: source
Thrown at crates/eww/src/widgets/mod.rs:41
.name("command-execution-thread".to_string())
.spawn(move || {
log::debug!("Running command from widget [timeout: {}ms]: {}", timeout.as_millis(), cmd);
let child = Command::new("/bin/sh").arg("-c").arg(&cmd).spawn();
match child {
Ok(mut child) => match child.wait_timeout(timeout) {
// child timed out
Ok(None) => {
log::error!("WARNING: command {} timed out", &cmd);
let _ = child.kill();
let _ = child.wait();
}
Err(err) => log::error!("Failed to execute command {}: {}", cmd, err),
Ok(Some(_)) => {}
},
Err(err) => log::error!("Failed to launch child process: {}", err),
}
})
.expect("Failed to start command-execution-thread");
}
fn replace_placeholders<T>(cmd: &str, args: &[T]) -> String
where
T: 'static + std::fmt::Display + Send + Sync + Clone,
{
if !args.is_empty() {
let cmd = cmd.replace("{}", &format!("{}", args[0]));
args.iter().enumerate().fold(cmd, |acc, (i, arg)| acc.replace(&format!("{{{}}}", i), &format!("{}", arg)))
} else {
cmd.to_string()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]View on GitHub (pinned to 48f5aa8b37)