BoundaryML/baml · error · anyhow::Error

Could not find an available port in range {}..{}

Error message

Could not find an available port in range {}..{}

What it means

pick_port scans up to max_attempts consecutive ports starting at base_port, binding a loopback TcpListener on each until one succeeds. If every candidate port fails to bind, it bails with this anyhow error. It exists so callers can auto-pick a free port starting from the default 4265.

Source

Thrown at baml_language/crates/baml_lsp_server/src/playground_server.rs:401

    RunOutcome::Failed(RunError {
        class: runtime_error_class(err),
        message: format!("{err}"),
        details: None,
        value_ref,
    })
}

/// Find an available TCP port starting from `base_port`.
pub async fn pick_port(base_port: u16, max_attempts: u16) -> anyhow::Result<(TcpListener, u16)> {
    for offset in 0..max_attempts {
        let port = base_port + offset;
        let addr = SocketAddr::from(([127, 0, 0, 1], port));
        match TcpListener::bind(addr).await {
            Ok(listener) => return Ok((listener, port)),
            Err(_) => continue,
        }
    }
    anyhow::bail!(
        "Could not find an available port in range {}..{}",
        base_port,
        base_port + max_attempts
    )
}

/// Resolve the given env var names against `lookup`, keeping only those set.
/// Pure so tests never have to mutate the process environment.
fn collect_referenced_env_vars(
    names: &[String],
    lookup: impl Fn(&str) -> Option<String>,
) -> std::collections::HashMap<String, String> {
    names
        .iter()
        .filter_map(|n| lookup(n).map(|v| (n.clone(), v)))
        .collect()
}

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Free the occupied ports: find and kill the processes listening in the range (lsof -i :4265-4290).
  2. Restart the client; pick_port retries and may find a freed port.
  3. Increase the scan range/max_attempts or start from a different base port via --port.
  4. Reboot or clean up leaked listeners if orphaned processes hold the ports.

Example fix

// before
let (listener, port) = pick_port(4265, 10).await?; // all 10 busy

// after
let (listener, port) = pick_port(4265, 100).await?; // wider scan range
Defensive patterns

Strategy: retry

Validate before calling

for port in base_port..base_port + max_attempts {
    if std::net::TcpListener::bind(("127.0.0.1", port)).is_ok() {
        break; // a port is free
    }
}

Try / catch

match pick_port(4265, 10).await {
    Ok((listener, port)) => serve(listener, port),
    Err(e) if e.to_string().contains("available port") => {
        eprintln!("all candidate ports busy; free ports 4265+ and retry");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling pick_port(base_port, max_attempts) when all ports in base_port..base_port+max_attempts are already bound by other processes (TcpListener::bind fails for each attempt).

Common situations: Many concurrent playground/LSP sessions on one machine exhausting the scanned range; a system service squatting on 4265 and neighbors; very small max_attempts with heavy port usage; leaked listeners from crashed previous runs.

Related errors


AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/ea7c6d86b965bf17. Report an issue: GitHub.