[BUG] No Cross-File Indexing

Resolved 💬 2 comments Opened Mar 6, 2026 by LoveMig6334 Closed Mar 6, 2026

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

  1. rootUri should be derived automatically: Claude Code should walk up the directory tree from the filePath parameter to find package.json or tsconfig.json and use that as rootUri.
  2. Workspace files should be indexed: After initialized, Claude Code should either send textDocument/didOpen for workspace files, or rely on the server's own tsconfig.json-based file discovery (which requires a valid rootUri).

Error Messages/Logs

Steps to Reproduce

Steps to Reproduce

  1. Install typescript-language-server globally (via mise .exe shims on Windows)
  2. Open a TypeScript/Next.js project in Claude Code
  3. Ask Claude Code to perform any LSP operation (e.g., "find all references to the Event type, using LSP")
  4. 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:

  1. Intercepts the initialize request and injects rootUri from process.cwd()
  2. After initialized, sends textDocument/didOpen for all .ts/.tsx files in the workspace
  3. 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); });

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗