xai-org/grok-build · error
transport is closed
Error message
transport is closed
What it means
An io::Error of kind NotConnection (NotConnected) returned when a write is attempted on an MCP transport whose writer half has already been closed or dropped. The guarded lock holds Option<Write>; None means the transport was shut down. It surfaces to callers of `start`-related transport write paths.
Source
Thrown at crates/codegen/xai-grok-mcp/src/servers.rs:2519
W: AsyncWrite + Send + Unpin + 'static,
{
type Error = std::io::Error;
fn send(
&mut self,
item: TxJsonRpcMessage<RoleClient>,
) -> impl Future<Output = Result<(), Self::Error>> + Send + 'static {
let lock = self.write.clone();
async move {
let mut bytes = serde_json::to_vec(&item).map_err(std::io::Error::other)?;
bytes.push(b'\n');
let mut guard = lock.lock().await;
match guard.as_mut() {
Some(write) => {
write.write_all(&bytes).await?;
write.flush().await
}
None => Err(std::io::Error::new(
std::io::ErrorKind::NotConnected,
"transport is closed",
)),
}
}
}
async fn receive(&mut self) -> Option<RxJsonRpcMessage<RoleClient>> {
loop {
let mut line = Vec::new();
match self.read.read_until(b'\n', &mut line).await {
Ok(0) => return None, // genuine end-of-stream
Ok(_) => {}
Err(e) => {
tracing::debug!(
server = %self.server_name,
error = %e,
"MCP stdio read error; closing transport",View on GitHub (pinned to bc7f02eddd)
Solutions
- Check session/transport state before writing; stop sending after shutdown is initiated
- Re-create the transport/session via start and retry the message
- Treat NotConnected as terminal and tear down the session cleanly
- Fix shutdown ordering so writers are closed only after pending sends complete
Example fix
// before
transport.write_all(&msg).await?; // NotConnected after shutdown
// after
if transport.is_open() {
transport.write_all(&msg).await?;
} else {
return Err(SessionError::Closed);
} Defensive patterns
Strategy: try-catch
Validate before calling
// check transport liveness before writing
if !session.transport_is_open() {
return Err(std::io::Error::new(std::io::ErrorKind::NotConnected, "transport is closed"));
} Type guard
fn is_writable(t: &Option<Transport>) -> bool {
matches!(t, Some(w) if !w.is_closed())
} Try / catch
match write_all(&bytes).await {
Err(e) if e.kind() == std::io::ErrorKind::NotConnected => {
// transport shut down; restart via start() or drop session
}
other => other?,
} Prevention
- Track session shutdown state and cancel pending sends on close
- Close transports only after all queued writes flush
- Avoid holding session handles past their shutdown
- Add connection-state assertions in debug builds
When it happens
Trigger: Writing to the MCP transport after the server/session was shut down, after the peer dropped the connection, or before the writer was installed (start not completed).
Common situations: Sending a response/notification after the client disconnected; racing shutdown with an in-flight write; reusing a session handle after close.
Related errors
- --env can only be used with stdio servers.
- blocking write task panicked: {e}
- workflow persistence channel closed
- {}
- cancelled during ignored-only copy
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/8d2dfea9a3890abe.
Report an issue: GitHub.