libnyanpasu/clash-nyanpasu · error

Port {} is not available

Error message

Port {} is not available

What it means

With ExternalControllerPortStrategy::Fixed, get_clash_external_port requires the configured port to be free; if local_port_available reports it occupied, it bails with this message. The function refuses to silently pick another port under a fixed strategy.

Source

Thrown at backend/tauri/src/utils/help.rs:164

    Ok(())
}

/// Resolve the UI language from the OS locale, as the canonical i18n key.
///
/// The key names a `rust_i18n` bundle in `backend/tauri/locales` and a paraglide
/// locale in the frontend; both use the same lowercase spelling.
pub fn detect_system_i18n_key() -> &'static str {
    nyanpasu_config::application::default_i18n_language().as_str()
}

pub fn get_clash_external_port(
    strategy: &ExternalControllerPortStrategy,
    port: u16,
) -> anyhow::Result<u16> {
    match strategy {
        ExternalControllerPortStrategy::Fixed => {
            if !port_scanner::local_port_available(port) {
                bail!("Port {} is not available", port);
            }
        }
        ExternalControllerPortStrategy::Random | ExternalControllerPortStrategy::AllowFallback => {
            if ExternalControllerPortStrategy::AllowFallback == *strategy
                && port_scanner::local_port_available(port)
            {
                return Ok(port);
            }
            let new_port = port_scanner::request_open_port()
                .ok_or_else(|| anyhow!("Can't find an open port"))?;
            return Ok(new_port);
        }
    }
    Ok(port)
}

pub fn resize_tray_image(img: &[u8], scale_factor: f64) -> Result<Vec<u8>> {
    let img = ImageReader::new(Cursor::new(img))

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Find and stop the process holding the port (netstat/Get-NetTCPConnection) — usually a stale clash core.
  2. Switch the strategy to AllowFallback or Random so a free port is chosen automatically.
  3. Change the configured external-controller port to an unused one.
  4. Ensure previous core instances are killed before restart (check the app's core lifecycle handling).

Example fix

// before
strategy: ExternalControllerPortStrategy::Fixed,
// after
strategy: ExternalControllerPortStrategy::AllowFallback, // fall back to a free port
Defensive patterns

Strategy: validation

Validate before calling

let port = parse_port(config.external_controller_port)?;
if strategy == ExternalControllerPortStrategy::Fixed
    && !port_scanner::local_port_available(port) {
    eprintln!("port {port} busy; free it or switch to AllowFallback before starting");
}

Try / catch

match get_clash_external_port(strategy, port) {
    Ok(p) => p,
    Err(e) if e.to_string().contains("is not available") => {
        get_clash_external_port(ExternalControllerPortStrategy::Random, port)?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling get_clash_external_port (via prepare_external_controller_port or patch_clash_with_rebuild) with strategy Fixed while another process — often a leftover clash/mihomo core or the app itself — is already bound to the port.

Common situations: Previous core process not fully terminated after a crash, two app instances running simultaneously, or the fixed port colliding with another service (e.g. another proxy panel).

Related errors


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