[BUG] No Cross-File Indexing
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Even after fixing rootUri (via a workaround proxy), Claude Code only sends textDocument/didOpen for the single queried file. Cross-file operations like findReferences and goToDefinition return empty results because the server has not indexed the rest of the workspace.
What Should Happen?
After injecting didOpen for all .ts/.tsx files in the workspace, all operations work correctly:
| Operation | Before fix | After fix |
|---|---|---|
| hover | Server crashed | Fully working with type info |
| documentSymbol | Server crashed | Lists all symbols |
| workspaceSymbol | Server crashed | 651 symbols across workspace |
| findReferences | Server crashed | 9 references across 5 files |
| goToDefinition | Server crashed | Fully working |
Windows Platform Note
On Windows, uv_spawn (used by child_process.spawn) cannot execute .cmd batch files or bash script shims — it requires a real .exe binary. Among tested package managers:
| Package manager | Shim type | Compatible with uv_spawn? |
|---|---|---|
| npm global | .cmd batch file | No |
| Volta | bash script shim | No |
| mise | .exe shim | Yes |
This means mise is currently the only viable way to install typescript-language-server globally on Windows for use with Claude Code's LSP tool.
Expected Behavior
rootUrishould be derived automatically: Claude Code should walk up the directory tree from thefilePathparameter to findpackage.jsonortsconfig.jsonand use that asrootUri.- Workspace files should be indexed: After
initialized, Claude Code should either sendtextDocument/didOpenfor workspace files, or rely on the server's owntsconfig.json-based file discovery (which requires a validrootUri).
Error Messages/Logs
Steps to Reproduce
Steps to Reproduce
- Install
typescript-language-serverglobally (via mise.exeshims on Windows) - Open a TypeScript/Next.js project in Claude Code
- Ask Claude Code to perform any LSP operation (e.g., "find all references to the Event type, using LSP")
- Observe: server crashes with "Could not find a valid TypeScript installation"
Claude Model
Sonnet (default)
Is this a regression?
No, this never worked
Last Working Version
_No response_
Claude Code Version
2.1.70
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
Windows Terminal
Additional Information
I created a stdio proxy that wraps typescript-language-server's cli.mjs entry point. The proxy:
- Intercepts the
initializerequest and injectsrootUrifromprocess.cwd() - After
initialized, sendstextDocument/didOpenfor all.ts/.tsxfiles in the workspace - Forwards all other messages unchanged
<details>
<summary>Proxy code (click to expand)</summary>
#!/usr/bin/env node
import { spawn } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import { readdirSync, readFileSync, statSync } from 'node:fs';
const __dirname = dirname(fileURLToPath(import.meta.url));
const realCli = join(__dirname, 'cli.original.mjs');
const cwd = process.cwd();
const rootUri = 'file:///' + cwd.replace(/\\/g, '/').replace(/ /g, '%20');
const IGNORE = new Set(['node_modules', '.next', '.git', 'dist', 'build', '.turbo', 'out']);
function getAllTsFiles(dir) {
const results = [];
for (const entry of readdirSync(dir)) {
if (IGNORE.has(entry)) continue;
const full = join(dir, entry);
try {
if (statSync(full).isDirectory()) results.push(...getAllTsFiles(full));
else if (/\.(ts|tsx)$/.test(entry)) results.push(full);
} catch { }
}
return results;
}
const server = spawn(process.execPath, [realCli, ...process.argv.slice(2)], {
env: process.env,
windowsHide: true,
stdio: ['pipe', 'pipe', 'inherit'],
});
server.stdout.pipe(process.stdout);
function sendToServer(msg) {
const out = JSON.stringify(msg);
server.stdin.write(`Content-Length: ${Buffer.byteLength(out, 'utf8')}\r\n\r\n${out}`);
}
function preOpenAllFiles() {
for (const file of getAllTsFiles(cwd)) {
try {
const text = readFileSync(file, 'utf8');
const uri = 'file:///' + file.replace(/\\/g, '/').replace(/ /g, '%20');
const languageId = file.endsWith('.tsx') ? 'typescriptreact' : 'typescript';
sendToServer({
jsonrpc: '2.0',
method: 'textDocument/didOpen',
params: { textDocument: { uri, languageId, version: 1, text } },
});
} catch { }
}
}
let buf = Buffer.alloc(0);
process.stdin.on('data', chunk => {
buf = Buffer.concat([buf, chunk]);
while (true) {
const sep = buf.indexOf('\r\n\r\n');
if (sep === -1) break;
const header = buf.slice(0, sep).toString('ascii');
const m = header.match(/Content-Length:\s*(\d+)/i);
if (!m) { buf = buf.slice(sep + 4); continue; }
const contentLength = parseInt(m[1]);
const bodyStart = sep + 4;
if (buf.length < bodyStart + contentLength) break;
const body = buf.slice(bodyStart, bodyStart + contentLength).toString('utf8');
buf = buf.slice(bodyStart + contentLength);
let msg;
try { msg = JSON.parse(body); } catch { continue; }
if (msg.method === 'initialize' && msg.params && !msg.params.rootUri) {
msg.params.rootUri = rootUri;
msg.params.rootPath = cwd;
}
sendToServer(msg);
if (msg.method === 'initialized') preOpenAllFiles();
}
});
process.stdin.on('end', () => server.stdin.end());
server.on('exit', code => process.exit(code ?? 0));
server.on('error', e => { process.stderr.write('proxy error: ' + e.message + '\n'); process.exit(1); });This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗