rust-lang/rust-analyzer · error · ServerError
proc-macro server closed the stream
Error message
proc-macro server closed the stream
What it means
`ServerError` with message `proc-macro server closed the stream` is produced by the bidirectional protocol's read loop when `postcard::read` returns EOF (`None`) from the proc-macro server's stdout stream: the server process stopped sending messages without a proper shutdown. It carries an `UnexpectedEof` io error as its cause.
Source
Thrown at crates/proc-macro-api/src/bidirectional_protocol.rs:48
pub type SubCallback<'a> = &'a dyn Fn(SubRequest) -> Result<SubResponse, ServerError>;
pub fn run_conversation(
writer: &mut dyn Write,
reader: &mut dyn BufRead,
buf: &mut Vec<u8>,
msg: BidirectionalMessage,
callback: SubCallback<'_>,
) -> Result<BidirectionalMessage, ServerError> {
let encoded = postcard::encode(&msg).map_err(wrap_encode)?;
postcard::write(writer, &encoded).map_err(wrap_io("failed to write initial request"))?;
loop {
let maybe_buf = postcard::read(reader, buf).map_err(wrap_io("failed to read message"))?;
let Some(b) = maybe_buf else {
return Err(ServerError {
message: "proc-macro server closed the stream".into(),
io: Some(Arc::new(io::Error::new(io::ErrorKind::UnexpectedEof, "closed"))),
});
};
let msg: BidirectionalMessage = postcard::decode(b).map_err(wrap_decode)?;
match msg {
BidirectionalMessage::Response(response) => {
return Ok(BidirectionalMessage::Response(response));
}
BidirectionalMessage::SubRequest(sr) => {
// TODO: Avoid `AssertUnwindSafe` by making the callback `UnwindSafe` once `SourceDatabase`
// becomes unwind-safe (currently blocked by `parking_lot::RwLock` in the VFS).
let resp = match catch_unwind(AssertUnwindSafe(|| callback(sr))) {
Ok(Ok(resp)) => BidirectionalMessage::SubResponse(resp),
Ok(Err(err)) => BidirectionalMessage::SubResponse(SubResponse::Cancel {
reason: err.to_string(),
}),
Err(_) => BidirectionalMessage::SubResponse(SubResponse::Cancel {View on GitHub (pinned to e8f7e90aa3)
Solutions
- Run `cargo clean` (or delete `target/`) so proc-macro dylibs are rebuilt for the current toolchain, then restart rust-analyzer.
- Update rust-analyzer and rebuild workspace dependencies so server and dylib ABI match.
- Reproduce the proc-macro crash with `cargo build` / `cargo expand` to find the offending macro crate and fix or pin it.
- Check for the server process being killed (OOM, antivirus, sandbox) in system logs.
- If it persists, disable proc-macro support (`rust-analyzer.procMacro.enable = false`) to keep other features working.
Example fix
// before # stale dylib in target/ rust-analyzer: proc-macro server started from old .so // after $ cargo clean $ # restart rust-analyzer so dylibs are rebuilt
Defensive patterns
Strategy: retry
Validate before calling
// before starting a proc-macro server, verify the dylibs match the current toolchain
fn dylibs_fresh(server_rustc: &str, workspace_dir: &Path) -> bool {
let fingerprint = workspace_dir.join("target/.rustc_info.json");
fingerprint.exists() && std::fs::read_to_string(fingerprint).map_or(false, |s| s.contains(server_rustc))
} Type guard
fn is_eof_server_error(e: &ServerError) -> bool {
e.message.contains("closed the stream")
&& e.io.as_ref().map_or(false, |io| io.kind() == std::io::ErrorKind::UnexpectedEof)
} Try / catch
match server.read_message(&mut buf) {
Err(e) if is_eof_server_error(&e) => {
tracing::warn!("proc-macro server died: {e}");
clean_target_dir_and_restart_server()?; // cargo clean, respawn process, retry once
}
other => other,
} Prevention
- `cargo clean` after every toolchain update so dylib ABI matches the server.
- Keep rust-analyzer and the workspace toolchain versions aligned.
- Watch for the proc-macro server process dying in system logs (OOM/AV kills).
- As a stopgap, disable proc-macro support in rust-analyzer settings.
When it happens
Trigger: The proc-macro server process exits or its stdout closes mid-session (crash, being killed, dylib load failure, or an ABI/version mismatch making the server bail immediately after start).
Common situations: Outdated proc-macro dylibs built with a different rustc version than the server; proc-macro server crashing on a malformed/expanding macro; sandbox/AV software killing the helper process; stale `target/` artifacts after a toolchain update.
Related errors
- bad kind {other}
- bad spacing {other}
- bad tag: {other}
- {punct:#?} is not a valid punct
- No file available to rename
AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03).
Data as JSON: /api/errors/09b57034aea5d5d7.
Report an issue: GitHub.