libnyanpasu/clash-nyanpasu · error

port already in use

Error message

port already in use

What it means

During a clash config patch, if the mixed-port value is being changed, the code first checks whether the new port is free on localhost using port_scanner::local_port_available. If the port is occupied, it discards the config draft and bails with "port already in use" so the core is never restarted onto a port that cannot bind.

Source

Thrown at backend/tauri/src/feat.rs:291

    let run = move || async move {
        let mixed_port = patch.get("mixed-port");
        let enable_random_port = Config::verge().latest().enable_random_port.unwrap_or(false);
        if let Some(mixed_port) = mixed_port
            && !enable_random_port
        {
            let changed = mixed_port
                != Config::verge()
                    .latest()
                    .verge_mixed_port
                    .unwrap_or(Config::clash().data().get_mixed_port());
            // 检查端口占用
            if changed
                && let Some(port) = mixed_port.as_u64()
                && !port_scanner::local_port_available(port as u16)
            {
                Config::clash().discard();
                bail!("port already in use");
            }
        };

        // 检测 external-controller port 是否修改
        if let Some(external_controller) = patch.get("external-controller") {
            let external_controller = external_controller.as_str().unwrap();
            let changed = external_controller != Config::clash().data().get_client_info().server;
            if changed {
                let (_, port) = external_controller.split_once(':').unwrap();
                let port = port.parse::<u16>()?;
                let strategy = Config::verge()
                    .latest()
                    .get_external_controller_port_strategy();
                if core_running && get_clash_external_port(&strategy, port).is_err() {
                    Config::clash().discard();
                    bail!("can not select fixed: current port is not available.");
                }
            }

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Pick a different mixed-port that is free (check with `netstat -ano | findstr <port>` / `ss -ltnp | grep <port>`).
  2. Kill the process occupying the port (stale clash/mihomo core or another proxy app) and retry the patch.
  3. Fully restart the app so lingering core processes are cleaned up, then apply the port change again.

Example fix

// before
let patch = serde_json::json!({ "mixed-port": 7890 });
patch_clash(patch).await?;
// after
if port_scanner::local_port_available(7890) {
    patch_clash(serde_json::json!({ "mixed-port": 7890 })).await?;
} else {
    eprintln!("port 7890 is taken; choose another");
}
Defensive patterns

Strategy: validation

Validate before calling

if let Some(port) = patch.get("mixed-port").and_then(|v| v.as_u64()) {
    if !port_scanner::local_port_available(port as u16) {
        eprintln!("port {} is already in use; pick another", port);
        return;
    }
}

Try / catch

match patch_clash(patch).await {
    Err(e) if e.to_string().contains("port already in use") => {
        // prompt user for a different mixed-port
    },
    other => other?,
}

Prevention

When it happens

Trigger: Calling patch_clash (via patch_clash_with_rebuild) with a patch containing a `mixed-port` that differs from the current value and is already bound by another process (another proxy, mihomo instance, dev server, or a stale core process).

Common situations: Users setting mixed-port to a value already used by another local service; two copies of the app (or clash-verge/mihomo) running simultaneously; a zombie core process still holding the old/new port; configuring a port below 1024 on systems with restrictions.

Related errors


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