From fcc4bbac9960a241bfaf481b8e287d304b464ca0 Mon Sep 17 00:00:00 2001 From: Neil Date: Mon, 14 Sep 2026 04:16:01 -0700 Subject: [PATCH] test(git): verify native Windows exited-child termination guard --- .../git-command-termination-runtime.yml | 22 +++++++++++++++ .../spawned-command-tree-kill.test.ts | 27 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 .github/workflows/git-command-termination-runtime.yml diff --git a/.github/workflows/git-command-termination-runtime.yml b/.github/workflows/git-command-termination-runtime.yml new file mode 100644 index 00000000000..1602bae956a --- /dev/null +++ b/.github/workflows/git-command-termination-runtime.yml @@ -0,0 +1,22 @@ +name: Git command termination runtime +on: + pull_request: + paths: + - 'src/main/git/command-runner/spawned-command-tree-kill*' + - '.github/workflows/git-command-termination-runtime.yml' + workflow_dispatch: +permissions: + contents: read +jobs: + windows-exit: + runs-on: windows-latest + timeout-minutes: 20 + env: + ORCA_BACKGROUND_LAUNCH: '1' + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + - uses: ./.github/actions/install-node-dependencies + - name: Verify exited native child does not trigger taskkill + run: node node_modules/vitest/vitest.mjs run --config config/vitest.config.ts src/main/git/command-runner/spawned-command-tree-kill.test.ts diff --git a/src/main/git/command-runner/spawned-command-tree-kill.test.ts b/src/main/git/command-runner/spawned-command-tree-kill.test.ts index b93a74a4f54..758fb296227 100644 --- a/src/main/git/command-runner/spawned-command-tree-kill.test.ts +++ b/src/main/git/command-runner/spawned-command-tree-kill.test.ts @@ -1,4 +1,6 @@ import { ChildProcess } from 'node:child_process' +import { once } from 'node:events' +import { spawnProcess } from '../../../shared/child-process/run-process' import type * as NodeChildProcess from 'node:child_process' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' @@ -92,4 +94,29 @@ describe('Git command tree termination', () => { expect(child.kill).toHaveBeenCalledOnce() expect(spawnMock).not.toHaveBeenCalled() }) + it.skipIf(originalPlatform !== 'win32').each([0, 128])( + 'does not taskkill an actual native Windows child after exit %i', + async (exitCode) => { + const original = await vi.importActual('node:child_process') + spawnMock.mockImplementation((program, args, options) => { + if (program !== process.execPath) { + throw new Error('Unexpected external process in native exit probe') + } + return original.spawn(program, args, options) + }) + const child = spawnProcess({ + program: process.execPath, + args: ['-e', `process.exit(${exitCode})`] + }) + const closed = once(child, 'close') + await once(child, 'exit') + expect(child.exitCode).toBe(exitCode) + expect(child.pid).toBeGreaterThan(0) + spawnMock.mockClear() + await killSpawnedCommandTree(child) + expect(spawnMock).not.toHaveBeenCalled() + expect(admitMock).not.toHaveBeenCalled() + await closed + } + ) })