libnyanpasu/clash-nyanpasu · error · anyhow::Error
Widget process exited: {}
Error message
Widget process exited: {} What it means
WidgetManager::start races the IPC server connect against the child widget process exit via tokio::select!. If the spawned `statistic-widget` child process terminates before the IPC handshake completes, `child.wait()` resolves with a success status and the manager aborts startup, reporting the exit status in this error. It signals the widget subprocess crashed or exited early instead of connecting back.
Source
Thrown at backend/tauri/src/widget.rs:132
.env("NYANPASU_EGUI_IPC_SERVER", server_name)
.env("NYANPASU_EGUI_WINDOW_STATE_PATH", widget_win_state_path)
.stdin(std::process::Stdio::inherit())
.stdout(os_pipe::dup_stdout()?)
.stderr(os_pipe::dup_stderr()?)
.spawn()
.context("Failed to spawn widget process")?;
tracing::debug!("Waiting for widget process to start...");
let tx = tokio::select! {
res = tokio::task::spawn_blocking(move || {
ipc_server
.connect()
.context("Failed to connect to widget")?;
ipc_server.into_tx().context("Failed to get ipc sender")
}) => res.context("Failed to get ipc sender")??,
res = child.wait() => {
match res {
Ok(status) => {
return Err(anyhow::anyhow!("Widget process exited: {}", status));
}
Err(e) => {
return Err(anyhow::anyhow!("Failed to wait for widget process: {}", e));
}
}
}
};
instance.replace(WidgetManagerInstance { tx, process: child });
Ok(())
}
pub async fn stop(&self) -> anyhow::Result<()> {
let Some(mut instance) = self.instance.lock().await.take() else {
tracing::debug!("Widget instance is not exists, skipping...");
return Ok(());
};
if !instance.is_alive() {
tracing::debug!("Widget instance is not alive, skipping...");View on GitHub (pinned to f7dbce2997)
Solutions
- Read the reported exit status: a non-zero code means the widget subprocess crashed — check stdout/stderr (they are inherited) for a panic message from the widget itself
- Verify the current executable supports the `statistic-widget <variant>` subcommand (the widget re-executes the same binary)
- Check/clear the widget state file `app_data_dir/widget_<variant>.state` which is passed via NYANPASU_EGUI_WINDOW_STATE_PATH
- Ensure a display/GPU environment is available where the egui widget can open a window
- Retry start() after fixing the environment; the manager replaces the instance only on success
Example fix
// before
Err(anyhow::anyhow!("Widget process exited: {}", status))
// after
Err(anyhow::anyhow!("Widget process exited before IPC handshake (status: {}); check widget stderr for a startup panic", status)) Defensive patterns
Strategy: try-catch
Validate before calling
// before start(): ensure the widget subcommand exists and state file is removable
let exe = tauri_utils::platform::current_exe()?;
let state_path = crate::utils::dirs::app_data_dir()?.join(format!("widget_{variant}.state"));
if !exe.exists() || std::env::var("DISPLAY").or_else(|_| std::env::var("WAYLAND_DISPLAY")).is_err() {
anyhow::bail!("widget prerequisites missing: display or executable unavailable");
} Type guard
fn widget_status_ok(status: std::process::ExitStatus) -> bool { status.success() } Try / catch
match manager.start(variant).await {
Err(e) if e.to_string().contains("Widget process exited") => {
log::error!("widget crashed at startup: {e:#}; check widget stderr");
}
Err(e) => return Err(e),
Ok(()) => {}
} Prevention
- Check stdout/stderr of the widget process (inherited) for the underlying crash cause
- Clear corrupted widget_<variant>.state files after updates
- Only spawn the widget in environments with a usable display
- Keep the executable and widget subcommand in sync across app updates
When it happens
Trigger: Calling WidgetManager::start when the spawned widget subprocess exits before completing the IPC handshake — e.g. the widget binary panics on startup, fails to parse env vars (NYANPASU_EGUI_IPC_SERVER / NYANPASU_EGUI_WINDOW_STATE_PATH), or the same executable lacks the `statistic-widget` subcommand.
Common situations: Broken or partially-updated install where the sidecar widget binary is corrupt; a missing GPU/display environment causing the egui widget to crash immediately; re-running `statistic-widget` manually and hitting an argument error; app updated while a stale widget state file confuses the widget.
Related errors
- IPC server is already initialized
- session state actor reply dropped
- Failed to wait for widget process: {}
- Failed to write to socket
- result.error
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/acfa2c851654a2cf.
Report an issue: GitHub.