BoundaryML/baml · error · anyhow::Error

Could not bind playground port {port}: {e}. Another process

Error message

Could not bind playground port {port}: {e}. Another process may be using it; pass a different --port or omit it to auto-pick from 4265.

What it means

bind_exact_port binds exactly the user-requested port on 127.0.0.1 and converts any bind failure into an actionable anyhow error. Unlike pick_port it never falls back to another port, because the user explicitly asked for this one.

Source

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

}

/// 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()
}

/// Bind exactly `port` on loopback, with an actionable error when taken.
pub async fn bind_exact_port(port: u16) -> anyhow::Result<TcpListener> {
    let addr = SocketAddr::from(([127, 0, 0, 1], port));
    TcpListener::bind(addr).await.map_err(|e| {
        anyhow::anyhow!(
            "Could not bind playground port {port}: {e}. Another process may be \
             using it; pass a different --port or omit it to auto-pick from 4265."
        )
    })
}

// ---------------------------------------------------------------------------
// Shared state for Axum handlers
// ---------------------------------------------------------------------------

/// Per-file mirror of the content the BROWSER currently has (set from
/// didOpen/didChange). The disk watcher consults it to avoid echoing the
/// browser's own write-throughs back as "external" changes — only content that
/// differs from the mirror is pushed to the browser. Shared between the `/api/lsp`
/// bridge (writer) and the disk watcher (reader). Keyed by canonical path.
pub type DocMirror = Arc<std::sync::Mutex<std::collections::HashMap<PathBuf, String>>>;

/// Custom LSP notification used to push external on-disk edits to the browser

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Pick a different port with --port N.
  2. Omit --port entirely so pick_port auto-selects from 4265 upward.
  3. Stop the conflicting process (lsof -i :<port> then kill it).
  4. Wait and retry if the previous session is shutting down.

Example fix

// before
baml lsp --port 4265   // 4265 already used by old session

// after
baml lsp --port 4280   // or omit --port to auto-pick
Defensive patterns

Strategy: fallback

Validate before calling

let free = std::net::TcpListener::bind(("127.0.0.1", requested_port)).is_ok();
if !free { eprintln!("port {requested_port} in use; will auto-pick"); }

Try / catch

match bind_exact_port(port).await {
    Ok(listener) => serve(listener),
    Err(e) if e.to_string().contains("Could not bind playground port") => {
        let (listener, p) = pick_port(4265, 100).await?;
        serve(listener)
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling bind_exact_port(port) when another process already holds that port (AddrInUse), or when binding fails for OS reasons (permissions, invalid address); typically after the user passed --port N to the playground.

Common situations: A previous playground/LSP session still running on 4265; another dev server (Vite, webpack) on the chosen port; Docker container publishing the same port; starting two LSP clients concurrently with the same explicit --port.

Related errors


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