gleam-lang/gleam · warning

joining_lsp_threads

Error message

joining_lsp_threads

What it means

After the LSP server exits, the CLI drops the connection and joins the stdio I/O threads with .expect("joining_lsp_threads"). This panics if any of the language-server transport threads ended with an error (e.g. the client closed the pipe abruptly or an I/O error occurred on stdin/stdout).

Solutions

  1. Shut down the LSP client gracefully (send an Exit notification) before terminating gleam lsp
  2. Restart the language server in the editor; the panic is a teardown-side symptom
  3. Check for broken-pipe conditions (reader process exiting early) in piped setups
  4. If reproducible with a specific editor, file a Gleam issue with client details

Example fix

// before: force-killing the server
$ kill -9 $(pgrep 'gleam lsp')
// after: let the client send Exit, or terminate politely
$ kill -TERM $(pgrep 'gleam lsp')
Defensive patterns

Strategy: try-catch

Try / catch

match io_threads.join() {
    Ok(()) => {},
    Err(e) => tracing::warn!(error = ?e, "lsp io thread join failed"),
}

Prevention

When it happens

Trigger: Running `gleam lsp` and the editor client terminates the connection ungracefully — killing the process, closing stdin mid-message, or an EPIPE on stdout — causing an io_threads.join() Err.

Common situations: Editor crashes or is force-quit while gleam lsp is running; language-client restarts that kill the transport; headless/CI usage piping the LSP where the reader closes the pipe early.

Related errors


AI-assisted analysis of gleam-lang/gleam@15b07c7830 (2026-09-14). Data as JSON: /api/errors/b803ca47e2e6a0b9. Report an issue: GitHub.

Appendix: source

Thrown at compiler-cli/src/lsp.rs:48

ignore this message.

If you have run `gleam lsp` yourself in your terminal then exit
this program by pressing ctrl+c.
"
        );
    }

    // Create the transport. Includes the stdio (stdin and stdout) versions but this could
    // also be implemented to use sockets or HTTP.
    let (connection, io_threads) = lsp_server::Connection::stdio();

    // Run the server and wait for the two threads to end, typically by trigger
    // LSP Exit event.
    LanguageServer::new(&connection, ProjectIO::new())?.run()?;

    // Shut down gracefully.
    drop(connection);
    io_threads.join().expect("joining_lsp_threads");

    tracing::info!("language_server_stopped");
    Ok(())
}

#[derive(Debug)]
pub struct LspLocker(BuildLock);

impl LspLocker {
    pub fn new(paths: &ProjectPaths, target: Target) -> Result<Self> {
        let build_lock = BuildLock::new_target(paths, Mode::Lsp, target)?;
        Ok(Self(build_lock))
    }
}

impl Locker for LspLocker {
    fn lock_for_build(&self) -> Result<LockGuard> {
        let guard: Guard = self.0.lock(&NullTelemetry)?;

View on GitHub (pinned to 15b07c7830)