镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
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

  1. ammerritt-gh commented on Sep 23, 2026

    @ammerritt-gh
    Author

    Previous discussion

    This builds on #2221, which is about Git for Windows' background detachment. In #2221 (comment), @mfriedrich74 noted: "PIDs can be reused by windows once a process is terminated and all process handles closed. I think to guarantee a PID belongs to the the desired process you must hold a valid process handle to it." In #2221 (comment), @dscho agreed about "all those PID problems" but was wary of the complexity of a Job Object approach.

    This report is an observed instance of that concern: unrelated, long-running processes terminated by the tree walk in terminate_process_tree() (compat/win32/exit-process.h), reached from mingw_kill() (compat/mingw.c) with exit code 128 + SIGTERM = 143. Notably, the git-heavy suite where we hit it also ran git maintenance run --auto --quiet --detach after every commit, so PID churn from the detach path is part of the environment.

    Workaround on our side

    We now start the long-running service so that every process in its ancestry stays alive for its lifetime (the chain is rooted at explorer.exe), so no ancestor's PID can be reused. Any other long-lived program whose parent has exited remains exposed whenever PID reuse coincides with git reaching this fallback with permission to terminate it.

  2. junkerderprovinz commented on Sep 27, 2026

    @junkerderprovinz

    Same thing here, with a different trigger and a victim that most Firefox users run.

    Git for Windows 2.54.0.windows.1, Windows 11 (build 26200). Firefox kept vanishing about every 20 minutes with exit code 143 and no crash report. Sysmon showed C:\Program Files\Git\mingw64\libexec\git-core\git.exe opening all 14 Firefox processes with PROCESS_TERMINATE (access 0x1) within 30 ms, followed by a ProcessTerminate for each of them.

    Firefox is exposed by default: firefox.exe first runs as a launcher process that starts the browser and exits, so the main browser process normally has a dead parent PID.

    On the git side there was a partial clone (--filter=blob:none) whose main pack had been truncated to 0 bytes while its .idx was intact. VS Code's git extension (autofetch on) kept running git for-each-ref --sort -committerdate ... in that repository, which triggered a lazy fetch (git -c fetch.negotiationAlgorithm=noop fetch origin ... --filter=blob:none --stdin) for the missing commit more than once a second. Each fetch died with "You are attempting to fetch ..., which is in the commit graph file but not in the object database" and on exit cleaned up its git remote-https child. The call stack of the ProcessAccess events, checked against the 2.54.0 binary, is exactly that: die() in fetch-pack.c, exit, cleanup_children(), mingw_kill(), then exit_process() falling back to terminate_process_tree(). With that many short-lived helpers, one of them sooner or later got Firefox's dead parent PID, and Firefox went down with all its content processes.

    This path exists because 2.54.0 marks the remote helper (transport-helper.c) and the connect.c child with clean_on_exit, from dd3693e ("transport-helper, connect: use clean_on_exit to reap children on abnormal exit"). Your ls-remote case goes through the connect.c half of the same change. Upstream reverted it in 8b44dee, and 2.55.0 no longer has either line, so these two triggers should be gone there. terminate_process_tree() itself hasn't changed, and git.c still uses clean_on_exit for dashed externals and aliases.

    So on 2.54.0 this doesn't need a 32-worker test suite. One broken partial clone in a folder that an editor autofetches is enough, and the victim can be any program that was started through a launcher which has since exited.

    Repairing the clone stopped it. The creation-time check your report points to (skip entries created before the process being terminated) would have prevented it.

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