Skip to content

terminate_process_tree() kills unrelated processes when a PID has been reused (no creation-time check) #6435

Description

@ammerritt-gh

Existing issues matching what you're seeing

  • I was not able to find an open or closed issue matching what I'm seeing

Git for Windows version

git version 2.54.0.windows.1
cpu: x86_64
built from commit: 2b8a3ab140826ac423c2845ef81d4c6ac4f7bf3c
sizeof-long: 4
sizeof-size_t: 8
shell-path: D:/git-sdk-64-build-installers/usr/bin/sh
rust: disabled
feature: fsmonitor--daemon
gettext: enabled
libcurl: 8.19.0
OpenSSL: OpenSSL 3.5.6 7 Apr 2026
zlib: 1.3.2
SHA-1: SHA1_DC
SHA-256: SHA256_BLK
default-ref-format: files
default-hash: sha1

Windows version

Windows 11

Windows CPU architecture

x86_64 (64-bit)

Additional Windows version information

Microsoft Windows [Version 10.0.26200.9457]

Options set during installation

Editor Option: Notepad++
Custom Editor Path: 
Default Branch Option: main
Path Option: Cmd
SSH Option: OpenSSH
Tortoise Option: false
CURL Option: WinSSL
CRLF Option: CRLFAlways
Bash Terminal Option: MinTTY
Git Pull Behavior Option: Merge
Use Credential Manager: Enabled
Performance Tweaks FSCache: Enabled
Enable Symlinks: Disabled
Enable FSMonitor: Disabled

Other interesting things

Only seen during a git-heavy parallel test suite (pytest with 32 xdist workers, each running many short git commands against temporary repositories), which recycles PIDs very quickly. The victim was a long-running process whose parent had exited long before (started via cmd /c start ... from a batch file). Nothing else on the machine kills processes; Sysmon (ProcessCreate + ProcessAccess) attributed the PROCESS_TERMINATE opens on both victims to C:\Utilities\Git\mingw64\bin\git.exe running ls-remote, with the call stack entirely inside git.exe. Both victims exited with code 143 and produced no crash event. Git config has nothing unusual: credential.helper=manager, no fsmonitor, no pager, no hooksPath.

Terminal/shell

None interactive: git was invoked from Python's subprocess module (CPython 3.12 on Windows, from a pytest suite), which runs C:\Utilities\Git\cmd\git.exe directly. Git then spawned C:/Utilities/Git/usr/bin/sh.exe for the upload-pack step.

Commands that trigger the issue

The triggering command, run in a repository that has no `origin` remote configured:

    git ls-remote origin main

Git treats `origin` as a local path, spawns `sh.exe -c "git-upload-pack 'origin'"`, the upload-pack fails, and on exit git terminates that sh.exe via mingw_kill(SIGTERM) -> terminate_process_tree(). The tree walk then also kills any unrelated process whose (dead) parent PID equals the PID Windows assigned to that sh.exe.

The PID collision cannot be forced, so there is no deterministic reproducer. The conditions that made it happen several times a day:

1. Start a long-running process through a parent that exits immediately, e.g. from cmd: `start "" pythonw.exe -m some_app`, so the long-running process keeps a dead parent PID.
2. In parallel, run many short git commands to churn PIDs, including repeated `git ls-remote origin main` in a remote-less repository (in our case a 32-worker pytest suite doing this among hundreds of other git calls per minute).
3. Eventually the sh.exe spawned for upload-pack receives the long-running process's dead parent PID; the long-running process and its descendants then exit with code 143.

A deterministic check of the underlying flaw: in terminate_process_tree(), any process whose th32ParentProcessID equals the target's PID is killed, even if it was created before the target, which a real child cannot be.

Expected behaviour

git ls-remote origin main in a repository without an origin remote fails with an error (e.g. "'origin' does not appear to be a git repository") and exits non-zero. When git cleans up the sh.exe it spawned for upload-pack, only that sh.exe and its genuine descendants are terminated. No process that git did not start, directly or indirectly, is affected.

Actual behaviour

The command fails as expected, but git's cleanup also terminated two unrelated processes that were never its descendants: a long-running pythonw.exe launcher (the venv redirector C:....venv\Scripts\pythonw.exe) and its child, the real interpreter (C:...\Python312\pythonw.exe). Both had been running for several minutes before this git command started.

Sysmon timeline (local time, 2026-09-23):

  • 19:38:48.377: ProcessCreate: git.exe ls-remote origin main (C:\Utilities\Git\mingw64\bin\git.exe), pid 61596
  • 19:38:48.398: ProcessCreate: sh.exe -c "git-upload-pack 'origin'", pid 68884, parent 61596. PID 68884 was also the recorded (long dead) parent PID of the pythonw launcher.
  • 19:38:48.485 / .486: ProcessAccess: git.exe pid 61596 opens the pythonw child and the pythonw launcher, GrantedAccess 0x1 (PROCESS_TERMINATE); the call stack is git.exe frames only, down to KERNELBASE
  • 19:38:48.492 / .494: ProcessTerminate: both pythonw processes exit with code 143 (128 + SIGTERM)

There is no crash event, no log line from the victims, and nothing else on the system touched them. We saw the same exit code 143 with the same silent disappearance several times the same day, each time during a parallel git-heavy test run.

Repository

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions