Desktop app spawns ~17 git processes per second continuously (Windows), amplifying a kernel pool leak into ~6 GB/day

Status Open
Maintainer reply None cached
Activity 11 comments · opened Sep 15, 2026

Summary

On Windows, the desktop app continuously spawns git.exe at a very high rate — measured 15–20 process starts per second, sustained, each with its own conhost.exe. That is ~2 million short-lived processes per day from a single open app.

This is wasteful on its own, but on this machine it also acts as a ~25× amplifier for a separate kernel-level defect (Windows Defender's WdFilter.sys leaks one process-object reference per created process), turning a negligible leak into ~6 GB of non-paged kernel pool per day, after which the machine can no longer shut down cleanly and has to be power-cycled.

Measurements

Process starts captured with WMI Win32_ProcessStartTrace (ordinary process sampling misses them — these processes live under 40 ms):

starts in 10 s: 297
  git.exe       191
  conhost.exe    97

Parent attribution, same trace:

git spawned by: claude (pid 19804)   97 times in 8 s

The parent is the app's node utility process:

claude.exe --type=utility --utility-sub-type=node.mojom.NodeService

The command being run repeatedly is a plain git invocation against the open project directory.

What does NOT change the rate

Each of these was measured before/after, 10-second windows:

| action | git starts / 10 s |
|---|---|
| baseline | 198 |
| archived every session except the current one | 198 |
| restarted the desktop app completely | 173 |
| closed the diff / changes panel | 152 |

So it is not a runaway loop accumulated over a long session, not per-session, and not tied to the diff panel being open. The app also keeps a change indicator on its icon, which may be what keeps the polling alive regardless of the panel.

Environment

  • Windows 10 22H2 (10.0.19045), 47 GB RAM
  • Claude Code desktop app 1.52386.6
  • One project open (a Go monorepo), git repo on a local NTFS disk

Why it matters beyond CPU

Every created process on this machine leaks its EPROCESS and token in the kernel pool (confirmed with LiveKD + object reference tracing: gflags -ko -t Proc, then !obtrace on a terminated process object — the outstanding reference is taken by WdFilter.sys via PsLookupProcessByProcessId). That is Microsoft's bug, not this app's. But the app's polling rate is what makes it fatal:

  • kernel pool with the app open, idle: +300–700 MB per hour
  • a full 13-minute CI run of the project: +206 MB (i.e. less than an hour of the app sitting idle)
  • non-paged pool reaches the danger zone (3–4 GB) within a working day, and the machine then fails to reboot from the OS

Suggested fix

Poll git on an event (file-system watch / focus change) or at a human interval (seconds, not 60 ms), and reuse a single long-lived git process where possible. Even a 1-second interval would be a 20× reduction.

View original on GitHub ↗

4 Comments

SrekelDGG · 23 days ago

I have a similar problem, not sure if it's the exact same bug, but here is the report straight from claude's mouth. Seems related to #94655 too.

Title: Desktop app spawns runaway orphaned git diff processes, possibly causing file write issues

The Claude Code desktop app (Windows) repeatedly spawned git.exe processes running the same command against the same commit, piling up to 100+ concurrent orphaned processes:

git.exe -c core.quotepath=false -c safe.directory=* -c core.fsmonitor=false -c branch.autoSetupMerge=false -c core.useReplaceRefs=false -c core.hooksPath=\\.\NUL -c filter.lfs.clean= -c filter.lfs.smudge= -c filter.lfs.process= -c filter.lfs.required=false -c diff.ignoreSubmodules=dirty -c diff.submodule=short -c status.submoduleSummary=false -c submodule.recurse=false -c log.showSignature=false -c alias.remote-denied_by_claude_desktop.command= -c help.autocorrect=0 diff --ignore-submodules=dirty --submodule=short --no-textconv --numstat -M <commit>

Repro/observations:

  • 3 active Code-tab sessions open, none of which had been asked to perform any git operations, and none related to the specific commit being diffed.
  • All ~100 processes targeted the exact same commit hash, each spawned by a distinct, already-exited parent process (so they were orphaned almost immediately after spawning — consistent with a retry loop rather than one hung process).
  • Exiting/restarting the Claude desktop app did not terminate the orphaned processes; they had to be force-killed manually via Stop-Process.
  • The -c alias.remote-denied_by_claude_desktop.command= flag confirms these came from the app's own git integration, not user/session-initiated git commands.
  • Suspect this runaway process accumulation is also the cause of files intermittently becoming write-restricted/locked in the working tree (reporter's observation — files appeared to become write-only/locked during this period).

Environment: Windows 11 Enterprise, Claude Code desktop app, Windows PowerShell + Git Bash.

phonon112358 · 16 days ago

Independent reproduction on a second machine, with a reference trace. Same pattern as this issue; the amplification here comes from the Claude Code engine's per-agent git calls rather than only from the desktop app's idle polling.

Environment

  • Windows 11 Pro 23H2, build 22631.6199 (no cumulative update since Nov 2025, so no recent Windows change)
  • Microsoft Defender platform 4.18.26080.3 installed 2026-09-03, 4.18.26080.4 installed 2026-09-18 (WdFilter.sys 4.18.26080.4, KslD.sys 1.1.26080.64012)
  • Claude desktop app (MSIX), Claude Code engine 2.1.284, Git for Windows (cmd\git.exe wrapper -> mingw64\bin\git.exe)
  • The user reports that the same multi-agent workloads ran without trouble until mid-September 2026.

Who creates the processes

Command lines captured by polling the process list every 15 ms (60 s windows):

| situation | git.exe starts / min | what |
|---|---|---|
| idle, four sessions open, no agents | 13 | the diff pane: merge-base, rev-list --left-right --count, ls-files --others, diff ... <commit> |
| ~16 subagents working (one workflow) | ~1,000 | per minute ~84x each of status --porcelain --ignore-submodules=dirty --untracked-files=normal, remote get-url origin, rev-parse --abbrev-ref HEAD, config -z --get-regexp ^(remote\..+\.(url\|pushurl)\|remote\.pushdefault\|branch\..+\.remote)$, symbolic-ref --short refs/remotes/origin/HEAD, plus ~92x status --porcelain=v2 -z --untracked-files=normal --ignored=no --no-renames --ignore-submodules=all |
| 19 subagents starting | 1,153 in one minute | same bundle |

So the five-command bundle runs roughly 5 times per minute per active agent, parented directly by the engine's claude.exe. Every call is three processes (wrapper git.exe, real git.exe, conhost.exe). git status also walks the whole working tree each time (here >10,000 files).

What each process costs

Every exited process stays behind as a process object: after two hours, 123,000 exited process objects against ~175 live processes, none freed between 5-minute censuses. A system-wide handle-table scan (SystemExtendedHandleInformation, matched on object address) found only 2 user-mode handles to them. Nonpaged pool grew ~6-7 KB per exited process (tag Proc ~3.7 KB, plus MiP2, File, ExSa, smSt in step, and ~2 kernel Event objects each). Over a 100-agent workflow: nonpaged pool 860 MB -> 1,480 MB in two hours. On 2026-09-27 two such workflows drove it to 5.7 GB and froze the machine.

Reference trace (gflags /ko /t Proc, then !obtrace in LiveKD)

One fresh cmd.exe process object after exit: HandleCount: 0 PointerCount: 1.

References: 34177, Dereferences 34176
Tag: Dflt References: 32776 Dereferences: 32775	Over reference by:  1

The unbalanced Dflt chain:

  • The first lookup is nt!PsLookupProcessByProcessId <- WdFilter <- nt!PspCallProcessNotifyRoutines. It moves the CID-table reserve onto the object (+32768).
  • At exit, nt!PspClearProcessThreadCidRefs returns 32705.
  • So 63 lookups went through the CID table, and 62 were released. Five further explicit +1 are balanced by their own -1: tcpip, NtOpenProcess, condrv, and win32k twice.
  • The only components that appear anywhere in the chain are Microsoft's: WdFilter, WdNisDrv, peauth, cng, tcpip, win32k, condrv and nt. No third-party driver (NVIDIA, AMD, ASUS) appears.
  • The individual lookups taken from the CID reserve are not logged; only their releases are. So the trace does not name the holder directly.
  • Indication (not proof): for every later thread, WdFilter releases two references in its thread-notify path, one at a shallow and one at a deeper call site. For the process's first thread (PspInsertThread during NtCreateUserProcess) it releases only the deeper one. That is consistent with the WdFilter finding in this issue.

Suggested fix on the Claude side (independent of the Defender bug)

  • Cache branch, remote URL and default branch per session instead of per agent turn.
  • Invoke mingw64\bin\git.exe directly, which saves the wrapper process and the conhost.
  • Or read the refs from .git without spawning a process.

Even with the kernel leak fixed, ~1,000 short-lived processes per minute, each walking a large working tree, is a heavy load on its own.

phonon112358 · 16 days ago

Follow-up to my comment above: a platform-version test on the same machine.

MpCmdRun -ResetPlatform plus a reboot put Defender back on the in-box platform. The service now runs 4.18.2201.11, and the loaded System32\drivers\WdFilter.sys and WdNisDrv.sys are also 4.18.2201.11 (from Jan 2022). Before the reset, WdFilter loaded from drivers\wd\ and was 4.18.26080.4.

Test: 200x cmd /c exit (400 processes including conhost), then \Objects\Processes minus live processes:

| Defender platform / loaded WdFilter | exited processes left behind |
|---|---|
| 4.18.26080.4 | +406 |
| 4.18.2201.11 (in-box) | +414 |

So the September Defender platform updates are not what introduced the leak on this machine. Either the reference is held in every WdFilter version, in which case the leak is old and only became expensive with the process rate, or the holder is another in-box component.

On this machine the likelier recent change is the rate of process creation: the per-agent git bundle described above, about 5 bundles per active agent per minute, three processes per git call. That makes reducing the git spawning on the Claude side the practical fix, independent of who holds the reference.

phonon112358 · 16 days ago

Second follow-up from the same machine: the per-agent git bundle is tied to the working directory being a git repository.

I ran a session with several subagents doing file work (OCR and renaming) in a folder that is not a git repository, and captured process starts for 60 s:

| agents running in ... | git.exe starts / min from the engine (claude.exe with --output-format stream-json) | from the desktop app's diff pane (other repo sessions open) |
|---|---|---|
| a large git repo (earlier comment) | ~1,000 (five-command bundle, ~5x per agent per minute) | ~13-20 |
| a non-git folder | 0 | 16-30 |

So the status --porcelain / remote get-url / rev-parse / config --get-regexp / symbolic-ref bundle comes from agent activity inside a repo only. For anyone hit by the kernel-pool growth, a practical workaround until the polling is reduced is to run agent-heavy work that does not need the repo from outside it.

For completeness, the other continuous spawner on this machine was not Claude-related. ASUS AsusFCNotification.exe (fast charging Notifier) started cmd.exe -> ITERHPGen.exe about 37 times per minute, roughly 110 processes per minute with the conhosts. On a system where every exited process leaks ~6-7 KB of nonpaged pool, that alone is ~1 GB per day. Stopping it and removing its Run entry took the idle spawn rate from ~165 to ~55 processes per minute.

Showing cached comments. Read the full discussion on GitHub ↗