Skip to content

Windows: idle app makes the whole PC unusable, background git spawns stall the desktop 25% of the time #12498

Description

@SkiTee3000

What happened

This is critical for me: T3 Code makes my whole PC unusable while it is open. With the app open and idle (no turn running), the entire Windows desktop lags: the mouse cursor moves in jerks and every application hitches, not just T3 Code. This is a powerful 16C/32T, 96 GB workstation sitting at about 12% total CPU, so nothing looks wrong in Task Manager. Closing T3 Code fixes it immediately, reopening it brings it back, and the first seconds after launch are the worst. The only workaround is not running the product, which is why I rank this above every other bug I have reported.

Diagnosis

The idle server starts 19 to 44 processes per second, all console programs (git.exe, a conhost.exe for each, taskkill.exe, gh.exe). Process start and exit on Windows contend for the win32k USER lock, which the input and window-manager path also needs. With that many short-lived console children the lock is unavailable often enough to drop frames system-wide.

A/B, same machine, same evening, only T3 Code closed in between (60 s probe each, probe in the repro section; the process rates were measured back to back):

app open, idle app closed
new processes on the machine 24.5/s 0.3/s
wall-clock time with the lock stalled 25.3% 0.5%
FindWindowW calls stalled over 4 ms 21.1% 0.3%
FindWindowW p99 16.2 ms 0.6 ms
control syscall p99 0.003 ms 0.002 ms

Same probe correlated with ETW process start/exit events, a separate 60 s run with the app open, in 100 ms buckets:

process events in bucket buckets worst FindWindowW latency (median) control syscall (median)
0 193 0.13 ms 0.001 ms
1-4 129 13.8 ms 0.001 ms
5-14 259 14.9 ms 0.002 ms
15+ 19 15.3 ms 0.002 ms
  • The control syscall never slows down (max 0.14 ms), so this is lock contention, not CPU starvation.
  • In that run 19% of calls stalled for more than 4 ms, 14.6 s of the 60 s in total (24%, matching the A/B run), each stall 15-19 ms: two to three dropped frames at 143 Hz, every time. 68% of all buckets contained at least one process event.
  • 10.7% of all busy CPU time on the machine was git.exe + conhost.exe inside win32kfull.sys.

Where the processes come from. A 20 s ETW trace counted 876 process starts (44/s); a 120 s count gave 2,258. The server's own children in those 20 s were 236 git, 26 taskkill and 11 gh; everything else is multiplication on top of that:

source tracked in share of the 876 starts
git command volume: PR sweeps, remote status, per-project identity probes #11220, #8949 236 direct git spawns, the driver of everything below
Git\cmd\git.exe launcher starts a second git.exe per command #11221 +236
a conhost.exe per console child #2537 roughly one per server spawn, ~270
taskkill /T /F on non-zero exits, each going through WMI #2537 26, plus WmiPrvSE.exe at 7.4% of busy CPU
git-credential-manager, sh, git-remote-https under fetches #11220 the remainder

The server also performs ~4,500 file opens per second resolving git on PATH before every spawn (#11221), and Defender follows all of it (7.7% of busy CPU).

This includes #11405: the 8-permit semaphore bounds concurrency, not volume, so the rate did not change.

It also amplifies any other system problem. Per-process cost on Windows is not constant: while an unrelated third-party service on this machine was leaking handles, process creation slowed from ~45 ms to 130-190 ms, and the same background traffic then consumed 6-16 cores of kernel time and froze the desktop outright.

Steps to reproduce

  1. Windows 11, Git for Windows installed the default way, desktop app with a few dozen projects (34 here, 46 threads). Leave it idle.
  2. Run the probe below for 60 s with the app open, then again with the app fully closed (tray included).
  3. Compare the stalled line and the process start rate.

Expected: an idle app is close to invisible to the rest of the machine. A measurable bar: with N projects and no turn running, under 1 process start per second on average, and the probe reports under 1% stalled.
Actual: 19-44 process starts per second, about 25% of wall-clock time stalled.

win32k-probe.ps1 (no admin, no dependencies, changes nothing)
param([int]$Seconds = 60)
Add-Type -TypeDefinition @'
using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; using System.Collections.Generic;
public static class Win32kProbe {
  [DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern IntPtr FindWindowW(string cls, string name);
  [DllImport("ntdll.dll")] static extern int NtQueryTimerResolution(out uint a, out uint b, out uint c);
  public static List<double[]> Run(int seconds) {
    var rows = new List<double[]>(); Thread.CurrentThread.Priority = ThreadPriority.Highest;
    double ms = 1000.0 / Stopwatch.Frequency; long end = Stopwatch.GetTimestamp() + (long)seconds * Stopwatch.Frequency; uint a, b, c;
    while (Stopwatch.GetTimestamp() < end) {
      long t0 = Stopwatch.GetTimestamp(); FindWindowW("NoSuchClass_probe", null);
      long t1 = Stopwatch.GetTimestamp(); NtQueryTimerResolution(out a, out b, out c);
      long t2 = Stopwatch.GetTimestamp();
      rows.Add(new double[] { (t1 - t0) * ms, (t2 - t1) * ms }); Thread.Sleep(1);
    }
    return rows;
  }
}
'@
$rows = [Win32kProbe]::Run($Seconds)
$u = $rows | % { $_[0] } | Sort-Object; $c = $rows | % { $_[1] } | Sort-Object
$stalls = @($u | ? { $_ -gt 4 }); $stalledMs = ($stalls | Measure-Object -Sum).Sum
"win32k call ms:  p50={0:N3}  p99={1:N2}  max={2:N2}" -f $u[[int]($u.Count*.5)], $u[[int]($u.Count*.99)], $u[-1]
"control call ms: p50={0:N3}  p99={1:N3}  max={2:N2}" -f $c[[int]($c.Count*.5)], $c[[int]($c.Count*.99)], $c[-1]
"stalls over 4 ms: {0} ({1:N1}% of calls), stalled {2:N1} s of {3} s = {4:N1}%" -f $stalls.Count, (100*$stalls.Count/$u.Count), ($stalledMs/1000), $Seconds, ($stalledMs/10/$Seconds)

Version

0.0.43-nightly.20260917.1851 (desktop). Relevant code unchanged on main @ 4d36142.

Environment

Windows 11 Pro 26200, 16C/32T, 96 GB, 143 Hz display, Defender real-time protection on, git 2.55 for Windows.

Evidence

OPEN:   24.5 new processes/s | stalls over 4 ms: 1313 (21.1% of calls), stalled 15.2 s of 60 s = 25.3%
CLOSED:  0.3 new processes/s | stalls over 4 ms:   26 ( 0.3% of calls), stalled  0.3 s of 60 s =  0.5%

busy CPU share, 30 s profile: git.exe 15.9%, conhost.exe 8.6%, WmiPrvSE.exe 7.4%,
  server 4.6%, git-credential-manager 2.4%, taskkill 1.5%, gh 1.3%

Related issues

#11220, #8949, #11221 and #2537 each track one cause in the code. None of them describes the user-visible result, that an idle app degrades the entire desktop, or the win32k mechanism, and none can be closed against a machine-level bar. This issue is that bar: fixing any one cause reduces the rate, but the symptom stays until the idle spawn rate is near zero. It is not a duplicate of #11220, which is about sweep cadence and cache expiry and would be satisfied by a change that still leaves tens of spawns per second from the other paths.

Fix applied or workaround

Closing T3 Code. Nothing was changed on the machine.

Filed by

Claude Code (Claude Fable 5.1), following the t3 triage playbook.

Activity

  1. juliusmarminge commented on Sep 18, 2026

    @juliusmarminge
    Member

    Triage

    Confirmed against 0.0.43-nightly.20260917.1851 and current main @ 4d36142c02 (same SHA the report cites). Nothing since that nightly reduces idle spawn volume. The A/B + ETW numbers are enough to treat this as a real Windows desktop-wide input stall, not CPU starvation and not “T3 feels slow.”

    The control syscall never moves (p99 0.003 ms vs 0.002 ms). FindWindowW does (p99 16.2 ms vs 0.6 ms; 25.3% vs 0.5% of wall-clock stalled). Process-start rate with the app idle is 24.5/s vs 0.3/s closed. That is win32k USER-lock contention from short-lived console children.

    What the code does today

    Idle spawn demand is still on by default. The desktop being open (no turn running) is enough.

    1. Background loops run without client demand.

    ThreadPullRequestReactor.start is explicit: “Run without client demand,” then Schedule.spaced("1 minute") after a one-minute delay. ThreadSettlementReactor and PullRequestSyncReactor use the same spaced minute. None of the orchestration reactors consult BackgroundPolicy. VCS status polling does (shouldRunScopeWork({ type: "vcs-status" })), and with the desktop window open that lease is live, so VcsStatusBroadcaster also ticks at DEFAULT_VCS_STATUS_REFRESH_INTERVAL = 30 s, and GitVcsDriverCore can refresh upstream every STATUS_UPSTREAM_REFRESH_INTERVAL = 15 s.

    2. The caches are sized to expire before the next pass.

    Cache TTL on main Effect on an idle minute
    PR_LOOKUP_CACHE_TTL (GitManager.ts) 60 s, “matches the automatic settlement sweep” Next spaced pass starts after 60 s plus the previous pass, so the entry is already cold
    RepositoryIdentityResolver positive TTL 1 minute Same: identity is re-resolved every sweep
    repository-root cache, null result Duration.zero Non-git / failed roots are re-probed on every read
    VcsDriverRegistry DETECTION_CACHE_TTL 2 seconds Any status/snapshot read more than 2 s later re-runs rev-parse --is-inside-work-tree, --show-toplevel, --git-common-dir

    branchPullRequest still does uncached git remote + for-each-ref before the 60 s PR cache, and readConfigValue / git config --get is still uncached. Settlement can also call branchPullRequest(..., { refresh: true }), which drops the PR cache and talks to gh again.

    3. Windows multiplies every spawn.

    • processRunner.runProcessCore always goes through resolveSpawnCommand. On win32 that uses resolveSpawnExecutableWithNode: a synchronous PATH × PATHEXT statSync walk. The 30 s CommandResolutionCache is only on resolveCommandPath / isCommandAvailable, not on the spawn path. Default Git for Windows puts Git\cmd\git.exe first; that launcher starts a second mingw64\bin\git.exe.
    • processRunner does not pass windowsHide. Console children still get a conhost.exe. Timeouts and scoped teardown go through Effect’s Windows kill path (taskkill /T /F via exec), which is [Bug]: Frequent cmd.exe / conhost flashes on Windows from provider probe & VCS process kill paths #2537.
    • Fetches pull in git-credential-manager / sh / git-remote-https on top.

    So “236 git + 26 taskkill + 11 gh in 20 s → 876 process starts (44/s)” is the expected shape: demand from the loops, then ×2 for the launcher, then ×1 conhost per console child, then taskkill/WMI on teardown.

    4. #11405 does not change this ticket.

    GitVcsDriverCore (Semaphore.makeUnsafe(8)) and VcsProcess (VCS_PROCESS_CONCURRENCY = 8) only cap in-flight short commands. They do not cap starts per second. The 8-permit pool is why the rate survived that PR.

    Related, not duplicates

    Work What it tracks Why it does not close this
    #11220 Sweep cadence + 60 s caches that expire before the next pass A slower cadence or a longer TTL can still leave tens of spawns/s from identity, VCS detect, status, launcher, and conhost
    #8949 Identity / VCS detect spawning git on snapshot reads (shellSnapshot latency) Client-path slowness. The root cache was added after that report; it is still 1 minute / zero-for-null, and the user-visible failure here is the desktop lock, not the 15 s toast
    #11221 Uncached spawn PATH walk + Git\cmd launcher Multiplier only. Removing the extra statSync / picking mingw64 git does not stop the demand
    #2537 conhost / taskkill flashes Multiplier + visible flash. Hiding windows does not free the USER lock
    #11405 8-slot git burst cap (merged 2026-09-15) Concurrency bound, not volume. Already in this nightly

    Those tickets stay open as the implementation slices. This one is the machine-level bar they cannot be closed against: with N projects and no turn running, idle process-start rate near zero, probe stalled time under 1%.

    Workaround

    Quit T3 Code completely, tray included. There is no in-app idle/pause switch that stops the orchestration loops.

    Decision

    Accept. Keep #11220, #8949, #11221, and #2537 open. Do not close this as a duplicate of #11220.

    A change is not done for this issue until an idle Windows desktop with a few dozen projects is close to invisible on the probe in the report (< 1 process start/s, < 1% stalled). Fixing one cause is not enough if the remaining paths still stall FindWindowW.

  2. added
    bugSomething is broken or behaving incorrectly.
    acceptedfeature request accepted
    via-triageFiled through npx t3 triage
    on Sep 18, 2026
  3. davea38 commented on Sep 19, 2026

    @davea38

    Also getting this - there is some crazy lag going on making the app intermittently freeze

  4. SkiTee3000 commented on Sep 19, 2026

    @SkiTee3000
    ContributorAuthor

    Pull requests are open for three of the causes in the triage above. They touch different files and can merge in any order.

    Written by Claude Code (Claude Fable 5.1).

  5. CountMyBands commented on Oct 2, 2026

    @CountMyBands

    Also getting this - there is some crazy lag going on making the app intermittently freeze

    Same here it's really annoying when you're typing and the window just freezes and text just appears...

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

    acceptedfeature request acceptedbugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions