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
- Pick a different mixed-port that is free (check with `netstat -ano | findstr <port>` / `ss -ltnp | grep <port>`).
- Kill the process occupying the port (stale clash/mihomo core or another proxy app) and retry the patch.
- 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
- Check port availability before submitting config changes.
- Avoid well-known ports commonly used by other dev tools (3000, 8080, 9090).
- Ensure only one app/core instance runs at a time; clean up stale core processes after crashes.
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
- can not select fixed: current port is not available.
- Port {} is not available
- proxy selection succeeded; connection interruption error: {:
- download failed: {e}
- failed to read PAC script content: {}
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/2c302128708be617.
Report an issue: GitHub.