rust-lang/rust-analyzer · error
{loop_e} {join_e}
Error message
{loop_e}
{join_e} What it means
In `run_session`, when both the main loop (`crate::main_loop`) and the IO threads (`io_threads.join()`) fail, both errors are combined into one report via `anyhow::bail!("{loop_e}\n{join_e}")`. This exists so that an IO failure (e.g. broken pipe) that also kills the main loop does not hide either cause.
Source
Thrown at crates/rust-analyzer/src/session.rs:165
};
let not = lsp_server::Notification::new(
ShowMessageNotification::METHOD.into(),
ShowMessageParams { kind: MessageType::Warning, message: notice },
);
connection.sender.send(lsp_server::Message::Notification(not)).unwrap();
}
if config.discover_workspace_config().is_none()
&& !config.has_linked_projects()
&& config.detached_files().is_empty()
{
config.rediscover_workspaces();
}
// If the io_threads have an error, there's usually an error on the main
// loop too because the channels are closed. Ensure we report both errors.
match (crate::main_loop(config, connection), io_threads.join()) {
(Err(loop_e), Err(join_e)) => anyhow::bail!("{loop_e}\n{join_e}"),
(Ok(_), Err(join_e)) => anyhow::bail!("{join_e}"),
(Err(loop_e), Ok(_)) => anyhow::bail!("{loop_e}"),
(Ok(_), Ok(_)) => {}
}
tracing::info!("server did shut down");
Ok(())
}
fn patch_path_prefix(path: PathBuf) -> PathBuf {
use std::path::{Component, Prefix};
if cfg!(windows) {
// VSCode might report paths with the file drive in lowercase, but this can mess
// with env vars set by tools and build scripts executed by r-a such that it invalidates
// cargo's compilations unnecessarily. https://github.com/rust-lang/rust-analyzer/issues/14683
// So we just uppercase the drive letter here unconditionally.
// (doing it conditionally is a pain because std::path::Prefix always reports uppercase letters on windows)
let mut comps = path.components();View on GitHub (pinned to e8f7e90aa3)
Solutions
- Read both stacked messages: the second line (IO error) usually reveals the root transport failure (e.g. broken pipe)
- Fix the client to perform a proper shutdown/exit sequence (see the main-loop error)
- Check that the client keeps stdin/stdout open for the lifetime of the session
- If run under a supervisor, ensure the process is not being killed or disconnected externally
Defensive patterns
Strategy: try-catch
Try / catch
match run_session(config, connection).await {
Err(e) => eprintln!("session failed:\n{e:#}"), // both loop and io errors are in the chain
Ok(()) => {}
} Prevention
- Keep stdin/stdout pipes open for the whole session lifetime
- Perform the shutdown/exit handshake before dropping the connection
- Log the full anyhow chain to see both root causes
When it happens
Trigger: Calling `rust_analyzer::run_session` where `main_loop` returns `Err` (e.g. error 80) and simultaneously `io_threads.join()` returns `Err` because the client closed/errored the transport.
Common situations: Client terminates abruptly over stdio: read/write threads fail and the main loop's channel closes; transport (socket/pipe) dies mid-session.
Related errors
- client exited without proper shutdown sequence
- {join_e}
- {loop_e}
- No file available to rename
- inconsistent text range
AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03).
Data as JSON: /api/errors/f36e98018c5ee696.
Report an issue: GitHub.