ENABLE_LSP_TOOL: LSP subprocess stdin is immediately EOF, server shuts down before receiving any LSP messages
Summary
When ENABLE_LSP_TOOL=1 is set and an LSP server is configured via .lsp.json or plugin.json, Claude Code starts the server subprocess but closes the stdin pipe immediately — before sending the LSP initialize request. The server receives EOF on first readline() and shuts down.
Reproduction
Configure any pygls-based LSP server (e.g. java-functional-lsp) in .lsp.json:
{
"servers": [{
"name": "my-server",
"command": ["/path/to/server"],
"languages": ["java"],
"extensionToLanguage": {".java": "java"}
}]
}
Invoke any LSP tool (e.g. documentSymbol). Result: "No LSP server available for file type: .java".
Server log (/tmp/java-functional-lsp.log):
=== java-functional-lsp started at 2026-04-27T08:17:55Z ===
pygls.server INFO: Starting async IO server
pygls.server INFO: Shutting down the server
No initialize is logged between start and shutdown — the server exits before receiving any messages.
Root Cause Diagnosis
The server only shuts down when stdin receives EOF. Manual tests confirm:
# stdin = /dev/null → immediate shutdown (same as Claude Code's behavior)
binary </dev/null # "Starting async IO server" → "Shutting down"
# stdin = open pipe (empty) → server waits indefinitely ✅
sleep 3 | binary # "Starting async IO server" (waits, no shutdown)
# stdin = proper LSP-framed request → server responds correctly ✅
printf "Content-Length: %d\r\n\r\n%s" ${#MSG} "$MSG" | binary # works
This means Claude Code provides stdin as EOF (either stdio: 'ignore' for stdin, or closes the write-end of the pipe immediately after spawning the process). No LSP handshake (initialize / initialized) is ever sent.
Expected Behavior
Claude Code should keep the write-end of the stdin pipe open for the lifetime of the LSP server session, and send the standard LSP initialize request before making any tool calls.
Actual Behavior
- Server starts and immediately shuts down on stdin EOF
documentSymbol,goToDefinition,hover, etc. all return"No LSP server available"- The server startup is cached as failed; no retry within the session
Environment
- Claude Code with
ENABLE_LSP_TOOL=1 .lsp.jsonproject-level configurationjava-functional-lsp0.9.17 (pygls-based)- macOS 25.3.0 / Python 3.10.18
Workaround
Until fixed upstream, java-functional-lsp 0.9.18+ wraps sys.stdin.buffer with an _EternalStdinBuffer that blocks forever on EOF instead of propagating it, and start-lsp.sh routes stdin through a FIFO with a keepalive writer (tail -f /dev/null).
🤖 Diagnosed with Claude Code
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗