fix(daemon): launch LSP servers with caller env

This commit is contained in:
2026-05-02 15:28:25 -04:00
parent 306771f92a
commit 04fd520438
5 changed files with 80 additions and 20 deletions

View File

@@ -16,8 +16,8 @@ import { ServerNotFoundError } from "./types.ts";
import { findRoot, pathToUri, uriToPath } from "./root.ts";
// Is On PATH - Returns true if `cmd` resolves to an executable via the
// current PATH. Absolute/relative paths are checked directly.
function isOnPath(cmd: string): boolean {
// supplied PATH. Absolute/relative paths are checked directly.
function isOnPath(cmd: string, env: NodeJS.ProcessEnv): boolean {
const isExec = (p: string) => {
try {
fs.accessSync(p, fs.constants.X_OK);
@@ -29,9 +29,9 @@ function isOnPath(cmd: string): boolean {
if (cmd.includes(path.sep)) return isExec(cmd);
const exts =
process.platform === "win32"
? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";")
? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";")
: [""];
for (const dir of (process.env.PATH ?? "").split(path.delimiter)) {
for (const dir of (env.PATH ?? "").split(path.delimiter)) {
if (!dir) continue;
for (const ext of exts) {
if (isExec(path.join(dir, cmd + ext))) return true;
@@ -69,16 +69,17 @@ export class LspClient {
constructor(private readonly server: ServerConfig) {}
// Start - Spawns the server process and wires up JSON-RPC.
async start(rootDir: string): Promise<void> {
async start(rootDir: string, env: NodeJS.ProcessEnv): Promise<void> {
// Verify Binary On PATH - Fail fast with a clear message instead of
// letting spawn ENOENT surface as a generic error. It's the user's
// responsibility to have the server installed & on PATH.
if (!isOnPath(this.server.command)) {
// letting spawn ENOENT surface as a generic error. Resolution uses the
// caller/session env, not the daemon's launch-time env.
if (!isOnPath(this.server.command, env)) {
throw new ServerNotFoundError(this.server.command);
}
this.proc = spawn(this.server.command, this.server.args, {
stdio: ["pipe", "pipe", "pipe"],
cwd: rootDir,
env,
});
this.proc.on("error", (err) => {
process.stderr.write(
@@ -277,10 +278,11 @@ export class LspClient {
export async function startClientForFile(
server: ServerConfig,
filePath: string,
env: NodeJS.ProcessEnv = process.env,
): Promise<{ client: LspClient; uri: string; rootDir: string }> {
const rootDir = findRoot(filePath, server.rootMarkers);
const client = new LspClient(server);
await client.start(rootDir);
await client.start(rootDir, env);
const uri = client.openDocument(filePath);
// Wait For Workspace Load - gopls & friends reject requests with errors
// like "no views" until their initial load completes.