From a2c5cc8b0349cda37ff106077780282555cfdf63 Mon Sep 17 00:00:00 2001 From: Chuan-kai Lin Date: Thu, 16 Jul 2026 15:03:58 -0700 Subject: [PATCH] Handle CLI server stdin EPIPE on teardown The CLI server process's stdin can emit an asynchronous 'error' during teardown: killProcessIfRunning() writes a "shutdown" request and then immediately end()s and kill()s the process, so on Windows the buffered write can complete with EPIPE once the read end closes. With no listener this became an unhandled error that faulted whichever operation was running, causing the flaky "should restart the database and run a query" failures in the Windows cli-integration tests (the restart command also restarts the CLI server via cliServer.restartCliServer()). Attach an 'error' listener to the CLI server's stdin inside killProcessIfRunning(), scoped to teardown, before issuing the shutdown write. The process is killed and dereferenced immediately afterwards, so the listener is short-lived and does not accumulate. Because it only exists during teardown, stdin errors during an active command retain their existing behaviour and cannot be masked. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 90105d62-02ab-405e-9c95-10466c971814 --- extensions/ql-vscode/src/codeql-cli/cli.ts | 27 +++++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/extensions/ql-vscode/src/codeql-cli/cli.ts b/extensions/ql-vscode/src/codeql-cli/cli.ts index 57ab04d4903..970b05a8ae8 100644 --- a/extensions/ql-vscode/src/codeql-cli/cli.ts +++ b/extensions/ql-vscode/src/codeql-cli/cli.ts @@ -313,11 +313,26 @@ export class CodeQLCliServer implements Disposable { killProcessIfRunning(): void { if (this.process) { + const proc = this.process; + + // The shutdown write below is buffered and, on Windows, can flush + // asynchronously *after* we end()/kill() the process, closing the read + // end and producing an EPIPE 'error' on stdin. Without a handler that + // async error is unhandled and gets attributed to an unrelated + // in-progress operation. Attach a handler scoped to this teardown so it + // is swallowed here; the process is killed and dereferenced immediately + // afterwards, so the listener is short-lived and does not accumulate. + proc.stdin.on("error", (e) => { + void this.logger.log( + `CodeQL CLI Server shutdown stdin error (ignored): ${getErrorMessage(e)}`, + ); + }); + // Tell the Java CLI server process to shut down. void this.logger.log("Sending shutdown request"); try { - this.process.stdin.write(JSON.stringify(["shutdown"]), "utf8"); - this.process.stdin.write(this.nullBuffer); + proc.stdin.write(JSON.stringify(["shutdown"]), "utf8"); + proc.stdin.write(this.nullBuffer); void this.logger.log("Sent shutdown request"); } catch (e) { // We are probably fine here, the process has already closed stdin. @@ -328,10 +343,10 @@ export class CodeQLCliServer implements Disposable { } // Close the stdin and stdout streams. // This is important on Windows where the child process may not die cleanly. - this.process.stdin.end(); - this.process.kill(); - this.process.stdout.destroy(); - this.process.stderr.destroy(); + proc.stdin.end(); + proc.kill(); + proc.stdout.destroy(); + proc.stderr.destroy(); this.process = undefined; } }