libnyanpasu/clash-nyanpasu · critical

failed to start server

Error message

failed to start server

What it means

During Tauri app setup, the internal server (server::run) is started on a background thread; if it returns an error the .expect panics with 'failed to start server'. The server is essential (IPC/services depend on it), so the library treats a failed bind/start as fatal.

Source

Thrown at backend/tauri/src/lib.rs:333

                &["clash-nyanpasu", "clash"],
                move |request| {
                    log::info!(target: "app", "scheme request received: {:?}", request);
                    resolve::create_window(&handle.clone()); // create window if not exists
                    while !is_window_opened() {
                        log::info!(target: "app", "waiting for window open");
                        std::thread::sleep(std::time::Duration::from_millis(100));
                    }
                    log_err!(
                        crate::ipc::SchemeRequestReceivedEvent { url: request }.emit(&handle),
                        "failed to emit scheme-request-received event"
                    );
                }
            ));
            std::thread::spawn(move || {
                nyanpasu_utils::runtime::block_on(async move {
                    server::run(*server::SERVER_PORT)
                        .await
                        .expect("failed to start server");
                });
            });
            Ok(())
        });

    let app = builder
        .build(tauri::generate_context!())
        .expect("error while running tauri application");
    app.run(|app_handle, e| match e {
        tauri::RunEvent::ExitRequested { api, code, .. } if code.is_none() => {
            api.prevent_exit();
        }
        tauri::RunEvent::ExitRequested { .. } => {
            utils::help::cleanup_processes(app_handle);
        }
        tauri::RunEvent::WindowEvent { label, event, .. } if label == "main" => match event {
            tauri::WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
                core::tray::on_scale_factor_changed(scale_factor);

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Check what occupies the port (netstat) and kill the stale process, or free the port
  2. Use port 0 / a fallback port if the fixed port is taken, and persist the chosen port
  3. Detect an already-running instance first and forward activation to it instead of binding
  4. Add error propagation/logging before the expect to identify the underlying bind error

Example fix

// before
server::run(*server::SERVER_PORT).await.expect("failed to start server");
// after
if let Err(e) = server::run(*server::SERVER_PORT).await {
    log::error!("server failed to start: {e}");
}
Defensive patterns

Strategy: retry

Validate before calling

use std::net::TcpListener;
fn port_free(port: u16) -> bool {
    TcpListener::bind(("127.0.0.1", port)).is_ok()
}
// call before starting the app; if !port_free(SERVER_PORT), abort or pick fallback

Try / catch

match server::run(*server::SERVER_PORT).await {
    Ok(()) => {},
    Err(e) => log::error!("server start failed: {e}"), // surface bind error instead of panic
}

Prevention

When it happens

Trigger: server::run(*SERVER_PORT) fails — usually because SERVER_PORT is already bound by another process or a leftover instance of the app, or the async runtime inside the spawned thread fails to drive the server.

Common situations: Previous instance didn't shut down and still holds the port; two app instances racing for the same fixed port; firewall/AV blocking the bind; port conflict with another local service.

Related errors


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