BoundaryML/baml · info · LspError
Client closed
Error message
Client closed
What it means
LspError::ClientClosed signals that the client's outbound sink is gone, so the server can no longer deliver messages. thiserror renders the fixed message 'Client closed'. It is an expected lifecycle condition, not a bug in message handling.
Source
Thrown at baml_language/crates/baml_lsp/src/error.rs:22
/// Every failure a request or notification handler can report.
///
/// Serialized to the wire exclusively through [`LspError::to_response_error`];
/// the legacy `-32001 UnknownErrorCode` is never emitted.
#[derive(Debug, thiserror::Error)]
pub enum LspError {
#[error("{0}")]
NotificationExtractError(lsp_server::ExtractError<lsp_server::Notification>),
#[error("Notification not supported: {0}")]
NotificationNotSupported(String),
#[error("{0}")]
RequestExtractError(lsp_server::ExtractError<lsp_server::Request>),
#[error("Request not supported: {0}")]
RequestNotSupported(String),
#[error("Failed to serialize request result: {0}")]
RequestSerializeError(serde_json::Error),
/// The client's sink is gone; nothing more can be delivered.
#[error("Client closed")]
ClientClosed,
/// Bounded transport backpressure (LSP `RequestFailed`, `-32803`).
#[error("LSP outbound sink is saturated")]
OutboundSaturated,
/// A frame larger than the transport limit (LSP `RequestFailed`,
/// `-32803`).
#[error("LSP outbound frame exceeds the transport limit")]
OutboundOversized,
#[error("Invalid command arguments for command: {command}: {message}")]
InvalidCommandArguments { command: String, message: String },
#[error("File not found: {}", .0.display())]
FileNotFound(PathBuf),
#[error("Path is invalid: {}: {message}", path.display())]
InvalidPath { path: PathBuf, message: String },
/// The document's path is under no known source root.
#[error("No source root contains {}", .0.display())]
NoRootForPath(PathBuf),
/// Cancellation claimed the response while the request was queued orView on GitHub (pinned to bd85ce9dee)
Solutions
- Treat this as normal shutdown: cancel outstanding work and exit the handler loop
- Check sink/connection liveness before dispatching long-running work
- Log at info/debug level rather than surfacing it as a user-facing failure
Example fix
// before
send(response)?; // Err(ClientClosed) bubbles up
// after
if let Err(LspError::ClientClosed) = send(response) {
log::info!("client disconnected; dropping response");
return Ok(());
} Defensive patterns
Strategy: try-catch
Type guard
fn is_client_closed(e: &LspError) -> bool {
matches!(e, LspError::ClientClosed)
} Try / catch
if let Err(LspError::ClientClosed) = sink.send(msg) {
log::info!("client gone; aborting send loop");
return Ok(());
} Prevention
- Track connection liveness and cancel background work on disconnect
- Avoid spawning fire-and-forget sends that outlive the transport
When it happens
Trigger: Writing a response or notification after the client disconnected / the connection loop ended; sending on a closed channel backing the LSP sink during shutdown.
Common situations: Editor closed or LSP client killed while a long-running request (formatting, diagnostics) is still in flight; slow handler finishing after shutdown; tests tearing down the transport early.
Related errors
- {0}
- Notification not supported: {0}
- Request not supported: {0}
- Failed to serialize request result: {0}
- no filesystem is attached to this server ({})
AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12).
Data as JSON: /api/errors/43e0ee9481bf097e.
Report an issue: GitHub.