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

  1. 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
  2. Verify the current executable supports the `statistic-widget <variant>` subcommand (the widget re-executes the same binary)
  3. Check/clear the widget state file `app_data_dir/widget_<variant>.state` which is passed via NYANPASU_EGUI_WINDOW_STATE_PATH
  4. Ensure a display/GPU environment is available where the egui widget can open a window
  5. 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

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


AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08). Data as JSON: /api/errors/acfa2c851654a2cf. Report an issue: GitHub.