LSP: documentSymbol/hover return empty results (missing textDocument/didOpen)
Description
The LSP integration does not return results for documentSymbol, hover, workspaceSymbol, or goToDefinition operations, despite the LSP server running correctly and producing diagnostics.
Environment
- OS: Windows 10 (MSYS2/Git Bash)
- Claude Code version: Latest (as of 2026-03-08)
- Plugin:
php-lsp@claude-plugins-official(Intelephense 1.16.5) - Node: v20.19.5 (via nvm4w)
Steps to Reproduce
- Install
php-lspplugin:/plugin install php-lsp@claude-plugins-official - Install Intelephense globally:
npm install -g intelephense - Open a Laravel project
- Use the LSP tool:
```
LSP documentSymbol app/Model/User.php line:1 character:1
→ "No symbols found in document"
LSP hover app/Model/User.php line:13 character:40
→ "No hover information available"
```
Expected Behavior
documentSymbol should return class/method/property symbols. hover should return type information.
Actual Behavior
All interactive LSP operations return empty results. However, diagnostics DO work — Intelephense produces publishDiagnostics notifications (e.g., "Undefined type 'Illuminate\Foundation\Auth\User'") which Claude Code receives and displays.
Root Cause Analysis
I tested Intelephense directly by sending JSON-RPC messages via stdin/stdout and confirmed:
- The LSP server initializes correctly (13ms)
- Indexing starts and completes
documentSymbolreturns full results (namespace, class, properties, methods)- Diagnostics are empty (types resolve correctly once indexed)
The critical difference: in my direct test, I sent a textDocument/didOpen notification before querying documentSymbol. The LSP spec requires this — servers only track documents that have been explicitly opened.
Hypothesis: Claude Code's LSP client is not sending textDocument/didOpen before performing queries like documentSymbol or hover, so the server has no knowledge of the file content and returns empty results.
Additional Windows Issues
Two additional issues encountered on Windows:
uv_spawnENOENT: The plugin config uses"command": "intelephense"which fails on Windows because Node'schild_process.spawncan't resolve npm's shell script wrappers. It needs eitherintelephense.cmdornode <path>/intelephense.js. I worked around this by editingmarketplace.jsonto use"command": "node", "args": ["<path>/intelephense.js", "--stdio"].
- Restart message persists: The "1 LSP server(s) provided by plugins require restart to activate" message appears on every
/reload-pluginseven after a full exit and restart. This message never clears.
Test Script
Node.js script that demonstrates Intelephense working correctly with proper didOpen:
const { spawn } = require('child_process');
const child = spawn('node', ['path/to/intelephense.js', '--stdio']);
function send(msg) {
const json = JSON.stringify(msg);
child.stdin.write(`Content-Length: ${Buffer.byteLength(json)}\r\n\r\n${json}`);
}
// 1. Initialize
send({ jsonrpc: '2.0', id: 1, method: 'initialize', params: {
processId: process.pid,
rootUri: 'file:///path/to/project',
capabilities: { textDocument: { documentSymbol: { hierarchicalDocumentSymbolSupport: true } } }
}});
// 2. Initialized
setTimeout(() => send({ jsonrpc: '2.0', method: 'initialized', params: {} }), 1000);
// 3. Open document (REQUIRED for symbols/hover to work)
setTimeout(() => {
const content = require('fs').readFileSync('path/to/File.php', 'utf8');
send({ jsonrpc: '2.0', method: 'textDocument/didOpen', params: {
textDocument: { uri: 'file:///path/to/File.php', languageId: 'php', version: 1, text: content }
}});
}, 3000);
// 4. Now documentSymbol works
setTimeout(() => send({ jsonrpc: '2.0', id: 2, method: 'textDocument/documentSymbol', params: {
textDocument: { uri: 'file:///path/to/File.php' }
}}), 5000);
Summary
| Feature | Works? | Notes |
|---------|--------|-------|
| LSP server starts | Yes | After Windows .cmd workaround |
| Diagnostics | Yes | publishDiagnostics received by Claude Code |
| documentSymbol | No | Returns empty |
| hover | No | Returns empty |
| workspaceSymbol | No | Returns empty |
| goToDefinition | Not tested | Likely same issue |
The fix should be straightforward: send textDocument/didOpen with the file content before performing any document-level LSP operations.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗