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

  1. Run `cargo clean` (or delete `target/`) so proc-macro dylibs are rebuilt for the current toolchain, then restart rust-analyzer.
  2. Update rust-analyzer and rebuild workspace dependencies so server and dylib ABI match.
  3. Reproduce the proc-macro crash with `cargo build` / `cargo expand` to find the offending macro crate and fix or pin it.
  4. Check for the server process being killed (OOM, antivirus, sandbox) in system logs.
  5. 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

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


AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03). Data as JSON: /api/errors/09b57034aea5d5d7. Report an issue: GitHub.