{"record":{"id":"acfa2c851654a2cf","repo":"libnyanpasu/clash-nyanpasu","slug":"widget-process-exited","errorCode":null,"errorMessage":"Widget process exited: {}","messagePattern":"Widget process exited: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"backend/tauri/src/widget.rs","lineNumber":132,"sourceCode":"            .env(\"NYANPASU_EGUI_IPC_SERVER\", server_name)\n            .env(\"NYANPASU_EGUI_WINDOW_STATE_PATH\", widget_win_state_path)\n            .stdin(std::process::Stdio::inherit())\n            .stdout(os_pipe::dup_stdout()?)\n            .stderr(os_pipe::dup_stderr()?)\n            .spawn()\n            .context(\"Failed to spawn widget process\")?;\n        tracing::debug!(\"Waiting for widget process to start...\");\n        let tx = tokio::select! {\n            res = tokio::task::spawn_blocking(move || {\n                ipc_server\n                    .connect()\n                    .context(\"Failed to connect to widget\")?;\n                ipc_server.into_tx().context(\"Failed to get ipc sender\")\n            }) => res.context(\"Failed to get ipc sender\")??,\n            res = child.wait() => {\n                match res {\n                    Ok(status) => {\n                        return Err(anyhow::anyhow!(\"Widget process exited: {}\", status));\n                    }\n                    Err(e) => {\n                        return Err(anyhow::anyhow!(\"Failed to wait for widget process: {}\", e));\n                    }\n                }\n            }\n        };\n        instance.replace(WidgetManagerInstance { tx, process: child });\n        Ok(())\n    }\n\n    pub async fn stop(&self) -> anyhow::Result<()> {\n        let Some(mut instance) = self.instance.lock().await.take() else {\n            tracing::debug!(\"Widget instance is not exists, skipping...\");\n            return Ok(());\n        };\n        if !instance.is_alive() {\n            tracing::debug!(\"Widget instance is not alive, skipping...\");","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/widget.rs#L114-L150","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nErr(anyhow::anyhow!(\"Widget process exited: {}\", status))\n// after\nErr(anyhow::anyhow!(\"Widget process exited before IPC handshake (status: {}); check widget stderr for a startup panic\", status))","handlingStrategy":"try-catch","validationCode":"// before start(): ensure the widget subcommand exists and state file is removable\nlet exe = tauri_utils::platform::current_exe()?;\nlet state_path = crate::utils::dirs::app_data_dir()?.join(format!(\"widget_{variant}.state\"));\nif !exe.exists() || std::env::var(\"DISPLAY\").or_else(|_| std::env::var(\"WAYLAND_DISPLAY\")).is_err() {\n    anyhow::bail!(\"widget prerequisites missing: display or executable unavailable\");\n}","typeGuard":"fn widget_status_ok(status: std::process::ExitStatus) -> bool { status.success() }","tryCatchPattern":"match manager.start(variant).await {\n    Err(e) if e.to_string().contains(\"Widget process exited\") => {\n        log::error!(\"widget crashed at startup: {e:#}; check widget stderr\");\n    }\n    Err(e) => return Err(e),\n    Ok(()) => {}\n}","preventionTips":["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"],"tags":["process","ipc","rust"],"backgroundTag":"process-exited-unexpectedly","analyzedSha":"f7dbce2997c633e484f54788035e770b3ee99773","analyzedAt":"2026-09-08T01:24:59.197Z","contentChangedAt":"2026-09-08T01:24:59.197Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}