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
- Shut down the LSP client gracefully (send an Exit notification) before terminating gleam lsp
- Restart the language server in the editor; the panic is a teardown-side symptom
- Check for broken-pipe conditions (reader process exiting early) in piped setups
- 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
- Let the LSP client send Exit before killing the server
- Avoid SIGKILL on `gleam lsp`
- In piped setups, keep the reader alive until the server exits
- Update editor LSP client extensions
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
- channel buffer write
- Final result error writing
- unimplemented
- Writing warning to stderr
- aliased non constant value
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)