LSP client kills the server right after `exit`, so servers that save state on exit (jdtls) lose it
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code
What's Wrong?
When Claude Code stops a plugin LSP server (e.g. at the end of a session), the LSP client's stop() sends shutdown, then the exit notification, and right after that its finally calls kill() (SIGTERM) on the server process. It never gives the process a chance to exit by itself. shutdownTimeout only bounds the shutdown/exit exchange, not the process exit.
Some servers do real work between exit and process exit. Eclipse JDT LS answers shutdown immediately and saves the workspace (JDT build state, the Gradle/Buildship model, indexes) in a job that its exit handler waits for. The SIGTERM lands in the middle of that save, so the next session finds no saved state: jdtls re-syncs Gradle and rebuilds every project from scratch. On our 80-project Gradle build that's about 20 minutes of CPU at every session start.
What Should Happen?
After sending exit, wait for the server process to exit (bounded by the remaining shutdownTimeout, or a few seconds when it's unset) and only then send SIGTERM. That's what vscode-languageclient does: it checks the process 2 s after exit and terminates it only if it's still alive (checkProcessDied in client/src/node/main.ts).
Error Messages/Logs
jdtls workspace log of a Claude Code session ends with:
!MESSAGE >> shutdown
After it: 1 of 80 .metadata/.plugins/org.eclipse.core.resources/.projects/*/org.eclipse.jdt.core/state.dat,
0 files in .metadata/.plugins/org.eclipse.buildship.core/project-preferences.
Same server and workspace, client waits for the process after `exit`:
>> shutdown 11:04:53.102
>> exit 11:04:53.104
Shutdown received... waking up main thread 11:04:54.715
-> 80 state.dat files, 84 Buildship preference files, next start is incremental.
Steps to Reproduce
- Add a plugin with an
.lsp.jsonserver running jdtls (jdtls -data <dir>) for.java, open a Gradle project, wait for the build to finish. - End the session.
- Look at
<dir>/.metadata/.log(ends at>> shutdown) and at the missingstate.datfiles; the next session rebuilds everything.
Claude Model
Not sure / Multiple models
Is this a regression?
No, this never worked
Claude Code Version
2.1.284 (same stop() code in 2.1.294)
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
Other (tmux)
Additional Information
Code path in the bundle: createLSPClient → stop() → finally { Z(process, connection, { force: false }) } → process.kill().
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗