You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Every exec() from Git Bash leaks one kernel Token object (paged pool) until reboot: ~660k tokens / 1.3 GB after 3 weeks of agent use #6471
The machine runs AI coding agents (Claude Code, Codex), which run every shell command through Git Bash. That is hundreds of thousands of fork+exec cycles over a few weeks of uptime.
Root cause, in Windows: the console host takes the reference. Reported upstream at microsoft/terminal#20781.
Terminal/shell
Bash (usr\bin\bash.exe), started from PowerShell / Windows Terminal / an agent's process (no mintty involved).
Commands that trigger the issue
foriin$(seq 300);do /usr/bin/true;done
Measure kernel Token objects before and after. The self-contained PowerShell repro (no admin; counts tokens via NtQueryObject(ObjectTypesInformation), with a cmd.exe control) is in the upstream issue.
Expected behaviour
Kernel Token objects go back to baseline once the processes exit.
Actual behaviour
Every exec() from an MSYS2 process leaks one kernel Token object (~2 KB of paged pool) until reboot:
300 iterations
Token objects
control: cmd.exe → where.exe
−7 to +2
fork + exec (/usr/bin/true)
+238 to +345 (7 of 8 runs)
exec only (bash -c 'exec /usr/bin/true')
+54 to +354
fork only (( : ), no exec)
0 to +29
After 22 days of uptime there were ~660,000 Token objects (1.3 GB paged pool, Toke tag) but only ~2,650 token handles system-wide. The leaked tokens also keep logon sessions alive (277 stale sessions of a sandbox account whose commands run through MSYS).
An xperf object-reference trace shows that the leaked token is the primary token of the process that calls exec(). Its one unreleased reference is taken in conhost.exe by ConsoleControl::NotifyConsoleApplication (presumably NtUserConsoleControl, → win32kfull.sys) when that process connects to the console. The same reference is released for the exec'd program. So the bug is in Windows. But it is triggered specifically by how the MSYS2 runtime hands the console over during exec, so it may be avoidable in the runtime, and you will know best whether it is. Plain CreateProcess in every variant I tried does not leak.
I have a tentative fix in hand: cygwingitgadget/cygwin#15. If you have the time, please feel free to build an MSYS2 runtime with that patch and to test whether this fixes your issue.
@nonacc actually, I forgot that I had already ran a tentative build of the MSYS2 runtime with this fix: https://github.com/dscho/Cygwin-msys2-fork/actions/runs/38449886036 has an install artifact that contains a fixed msys-2.0.dll. Could you please (temporarily) exchange the copy of that DLL in your setup with that fixed version and verify that the token leak is gone?
Tested the fixed msys-2.0.dll from https://github.com/dscho/Cygwin-msys2-fork/actions/runs/38449886036 (3.6.11-426a8d2290bd668c0de8b57b3dfea51fdc4b5fc3) on Windows 11 26H2, build 10.0.26300, in a copy of Git for Windows 2.55.0.windows.5 (verified that bash.exe loads the fixed DLL). Unfortunately it does not stop the leak here.
Method: kernel Token object count via NtQueryObject(ObjectTypesInformation), 1000 runs of /usr/bin/true per measurement, each run in its own console window, 3 rounds alternating:
idle 60 s (background noise from other processes): -43, +650, +227
Note: without a console attached (bash started from a process that has none), neither runtime leaks, which matches the conhost root cause described above.
@martindroneharmony ah, I missed something important. You ran /usr/bin/true 1,000 times directly in separate console windows. That does not exercise exec() (_P_OVERLAY), so our fix in spawn.cc never executed.
Even more importantly, with the help of GPT-6.1 Sol I found https://learn.microsoft.com/en-gb/answers/questions/5952454/windows-11-25h2-26200-8655-unbounded-paged-pool-le, which seems to talk precisely about the same problem. And it suggests a work-around: Setting SPI_SETFOREGROUNDLOCKTIMEOUT to 0 via SystemParametersInfoW(), to bypass _CheckAllowForeground () and thereby stopping the leak. Here is a PowerShell command to do that, and according to my research you'd only need to run this once per logon session:
Note that setting ForegroundLockTimeout to 0 disables Windows' focus-stealing prevention, so this is merely a work-around:
Focus Stealing: Any background application or spawned process can immediately pop to the front and steal keyboard focus while you are typing in another window (rather than flashing on the taskbar).
Misdirected Keystrokes: Typing into an active window (editor, terminal) might accidentally send keystrokes or enter keys into a background window that suddenly takes the foreground.
For headless machines, CI runners, or dedicated developer/agent VMs, this side effect is harmless; on interactive desktop machines, unexpected pop-ups may interrupt typing.
@martindroneharmony could you try this work-around and report whether it fixes the token leak for you?
First a clarification: my earlier wording was misleading. Each measurement was one console window running a bash loop of 1,000 /usr/bin/true starts (for i in $(seq 1000); do /usr/bin/true; done), not 1,000 separate windows. So every start was a fork() followed by exec() inside bash.
The work-around stops the leak here (Windows 11 26H2, build 10.0.26300, Git for Windows 2.55.0.windows.5). Kernel Token objects per 1,000 starts, 3 rounds each:
ForegroundLockTimeout at its value before (2147483647):
runtime 3.6.10 (installed): +1390, +1138, +955
runtime 3.6.11 with your fix: +80, +1064, +1146
control, cmd.exe: -11, +98, +63
idle 60 s: -12, +379, +149
ForegroundLockTimeout set to 0 with your snippet (run from a foreground window; from a background process the call has no effect):
runtime 3.6.10 (installed): -83, +44, +55 and +10, -34, +28
runtime 3.6.11 with your fix: -8, +48, -49
control, cmd.exe: -7, +4, +55
idle 60 s: -50, +18, +51
Everything is within the idle noise with the timeout at 0. The bash loops also ran faster (about 50 s instead of 85 s per 1,000 starts).
One thing that may matter: the live value on this machine was 2147483647, while HKCU\Control Panel\Desktop\ForegroundLockTimeout holds the default 200000, so some program raised it at runtime. I don't know yet which one.
Happy to test more.
Update: the reason the value is 2147483647. Others report that on Windows 11 the live value is overwritten at every logon by win32kfull!LoadCPUserPreferences, which reads the registry and then stores 0x7FFFFFFF unconditionally, so the registry setting (200000 here, 0 for them) is ignored: bentoner/windows-token-leak#1 (26200.9550 and 26300.9550, with disassembly; the leak itself is traced there to a PsReferencePrimaryToken without a matching dereference in CForegroundLaunch::_CheckAllowForeground). That matches this machine: registry 200000, live value 2147483647.
So the work-around has to be re-applied after every logon, by a process that may take the foreground (from a background process the call is refused while the value is 2147483647). A non-elevated scheduled task at logon works here: it set the value from 2147483647 to 0 in a test run. Leaked tokens are only freed by a restart (with Fast Startup, "Shut down" keeps them).
Activity
dscho commentedon Oct 20, 2026
I have a tentative fix in hand: cygwingitgadget/cygwin#15. If you have the time, please feel free to build an MSYS2 runtime with that patch and to test whether this fixes your issue.
dscho commentedon Oct 20, 2026
@nonacc actually, I forgot that I had already ran a tentative build of the MSYS2 runtime with this fix: https://github.com/dscho/Cygwin-msys2-fork/actions/runs/38449886036 has an
installartifact that contains a fixedmsys-2.0.dll. Could you please (temporarily) exchange the copy of that DLL in your setup with that fixed version and verify that the token leak is gone?Cygwin: testsuite: Add regression test for token leak on exec
Cygwin: spawn: detach console on exec to avoid leaking token
martindroneharmony commentedon Oct 20, 2026
Tested the fixed msys-2.0.dll from https://github.com/dscho/Cygwin-msys2-fork/actions/runs/38449886036 (3.6.11-426a8d2290bd668c0de8b57b3dfea51fdc4b5fc3) on Windows 11 26H2, build 10.0.26300, in a copy of Git for Windows 2.55.0.windows.5 (verified that bash.exe loads the fixed DLL). Unfortunately it does not stop the leak here.
Method: kernel Token object count via NtQueryObject(ObjectTypesInformation), 1000 runs of /usr/bin/true per measurement, each run in its own console window, 3 rounds alternating:
Note: without a console attached (bash started from a process that has none), neither runtime leaks, which matches the conhost root cause described above.
Happy to test another build.
dscho commentedon Oct 21, 2026
@martindroneharmony ah, I missed something important. You ran
/usr/bin/true1,000 times directly in separate console windows. That does not exerciseexec()(_P_OVERLAY), so our fix inspawn.ccnever executed.Even more importantly, with the help of GPT-6.1 Sol I found https://learn.microsoft.com/en-gb/answers/questions/5952454/windows-11-25h2-26200-8655-unbounded-paged-pool-le, which seems to talk precisely about the same problem. And it suggests a work-around: Setting
SPI_SETFOREGROUNDLOCKTIMEOUTto 0 viaSystemParametersInfoW(), to bypass_CheckAllowForeground ()and thereby stopping the leak. Here is a PowerShell command to do that, and according to my research you'd only need to run this once per logon session:Note that setting
ForegroundLockTimeoutto0disables Windows' focus-stealing prevention, so this is merely a work-around:For headless machines, CI runners, or dedicated developer/agent VMs, this side effect is harmless; on interactive desktop machines, unexpected pop-ups may interrupt typing.
@martindroneharmony could you try this work-around and report whether it fixes the token leak for you?
martindroneharmony commentedon Oct 21, 2026
@dscho thank you, that was it.
First a clarification: my earlier wording was misleading. Each measurement was one console window running a bash loop of 1,000
/usr/bin/truestarts (for i in $(seq 1000); do /usr/bin/true; done), not 1,000 separate windows. So every start was a fork() followed by exec() inside bash.The work-around stops the leak here (Windows 11 26H2, build 10.0.26300, Git for Windows 2.55.0.windows.5). Kernel Token objects per 1,000 starts, 3 rounds each:
ForegroundLockTimeout at its value before (2147483647):
ForegroundLockTimeout set to 0 with your snippet (run from a foreground window; from a background process the call has no effect):
Everything is within the idle noise with the timeout at 0. The bash loops also ran faster (about 50 s instead of 85 s per 1,000 starts).
One thing that may matter: the live value on this machine was 2147483647, while HKCU\Control Panel\Desktop\ForegroundLockTimeout holds the default 200000, so some program raised it at runtime. I don't know yet which one.
Happy to test more.
Update: the reason the value is 2147483647. Others report that on Windows 11 the live value is overwritten at every logon by
win32kfull!LoadCPUserPreferences, which reads the registry and then stores 0x7FFFFFFF unconditionally, so the registry setting (200000 here, 0 for them) is ignored: bentoner/windows-token-leak#1 (26200.9550 and 26300.9550, with disassembly; the leak itself is traced there to aPsReferencePrimaryTokenwithout a matching dereference inCForegroundLaunch::_CheckAllowForeground). That matches this machine: registry 200000, live value 2147483647.So the work-around has to be re-applied after every logon, by a process that may take the foreground (from a background process the call is refused while the value is 2147483647). A non-elevated scheduled task at logon works here: it set the value from 2147483647 to 0 in a test run. Leaked tokens are only freed by a restart (with Fast Startup, "Shut down" keeps them).