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 browserView on GitHub (pinned to bd85ce9dee)
Solutions
- Pick a different port with --port N.
- Omit --port entirely so pick_port auto-selects from 4265 upward.
- Stop the conflicting process (lsof -i :<port> then kill it).
- 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
- Omit --port unless you specifically need a fixed port
- Check the port is free before launching (lsof -i :<port>)
- Shut down previous playground sessions before restarting
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
- Could not find an available port in range {}..{}
- {err}
- Playground server error: {e}
- network error fetching {url}: {source}
- HTTP {status} fetching {url}
AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12).
Data as JSON: /api/errors/94d5a3c706ebcda9.
Report an issue: GitHub.