authorgravatar for 76637177+SunkenInTime@users.noreply.github.comDara Adedeji <76637177+SunkenInTime@users.noreply.github.com> 2026-09-08 11:50:48-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-08 21:20:48+05:30
log47eed9face4186635488fba5f2494eddd13f6491
tree01662ea9c72ff295ec2f2ad48f0a4f94201d9d8e
parent061543e9e5b54ec0048725c37d52fef2962df173
signature Signed by PGP key B5690EEEBB952194

fix(server): stop Windows terminal processes when closing (#10771)


2 files changed, 49 insertions(+), 11 deletions(-)

apps/server/src/terminal/NodePtyAdapter.test.ts+43-8
...@@ -21,15 +21,50 @@ const spawn = vi.fn(() => ({...@@ -21,15 +21,50 @@ const spawn = vi.fn(() => ({
2121
22vi.mock("node-pty", () => ({ spawn }));22vi.mock("node-pty", () => ({ spawn }));
2323
24const testLayer = NodePtyAdapter.layer.pipe(24const makeTestLayer = (platform: NodeJS.Platform = "win32") =>
25 Layer.provide(25 NodePtyAdapter.layer.pipe(
26 Layer.mergeAll(26 Layer.provide(
27 NodeServices.layer,27 Layer.mergeAll(
28 Layer.succeed(HostProcessPlatform, "win32"),28 NodeServices.layer,
29 Layer.succeed(HostProcessArchitecture, "x64"),29 Layer.succeed(HostProcessPlatform, platform),
30 Layer.succeed(HostProcessArchitecture, "x64"),
31 ),
30 ),32 ),
31 ),33 );
32);34
35const testLayer = makeTestLayer();
36
37for (const platform of ["win32", "linux", "darwin"] as const) {
38 it.effect(`terminates through node-pty using ${platform} semantics`, () =>
39 Effect.gen(function* () {
40 const adapter = yield* PtyAdapter.PtyAdapter;
41 const process = yield* adapter.spawn({
42 shell: "test-shell",
43 cwd: ".",
44 cols: 80,
45 rows: 24,
46 env: {},
47 });
48 const nativeProcess = spawn.mock.results.at(-1)!.value;
49 nativeProcess.kill.mockImplementation((signal?: string) => {
50 if (platform === "win32" && signal) {
51 throw new Error("Signals not supported on windows.");
52 }
53 });
54
55 process.kill("SIGTERM");
56 process.kill("SIGKILL");
57 process.kill();
58
59 assert.deepEqual(
60 nativeProcess.kill.mock.calls,
61 platform === "win32"
62 ? [[undefined], [undefined], [undefined]]
63 : [["SIGTERM"], ["SIGKILL"], [undefined]],
64 );
65 }).pipe(Effect.provide(makeTestLayer(platform))),
66 );
67}
3368
34it.effect("spawns through the public adapter with the provided host references", () =>69it.effect("spawns through the public adapter with the provided host references", () =>
35 Effect.gen(function* () {70 Effect.gen(function* () {
apps/server/src/terminal/NodePtyAdapter.ts+6-3
...@@ -69,9 +69,11 @@ const ensureNodePtySpawnHelperExecutable = Effect.fn(function* () {...@@ -69,9 +69,11 @@ const ensureNodePtySpawnHelperExecutable = Effect.fn(function* () {
6969
70class NodePtyProcess implements PtyAdapter.PtyProcess {70class NodePtyProcess implements PtyAdapter.PtyProcess {
71 private readonly process: import("node-pty").IPty;71 private readonly process: import("node-pty").IPty;
72 private readonly platform: NodeJS.Platform;
7273
73 constructor(process: import("node-pty").IPty) {74 constructor(process: import("node-pty").IPty, platform: NodeJS.Platform) {
74 this.process = process;75 this.process = process;
76 this.platform = platform;
75 }77 }
7678
77 get pid(): number {79 get pid(): number {
...@@ -87,7 +89,8 @@ class NodePtyProcess implements PtyAdapter.PtyProcess {...@@ -87,7 +89,8 @@ class NodePtyProcess implements PtyAdapter.PtyProcess {
87 }89 }
8890
89 kill(signal?: string): void {91 kill(signal?: string): void {
90 this.process.kill(signal);92 // node-pty terminates the Windows process tree without a POSIX signal.
93 this.process.kill(this.platform === "win32" ? undefined : signal);
91 }94 }
9295
93 onData(callback: (data: string) => void): () => void {96 onData(callback: (data: string) => void): () => void {
...@@ -164,7 +167,7 @@ export const make = Effect.fn("NodePtyAdapter.make")(function* (...@@ -164,7 +167,7 @@ export const make = Effect.fn("NodePtyAdapter.make")(function* (
164 cause,167 cause,
165 }),168 }),
166 });169 });
167 return new NodePtyProcess(ptyProcess);170 return new NodePtyProcess(ptyProcess, platform);
168 }),171 }),
169 });172 });
170});173});