Repository navigation
macOS Desktop: main thread spawns git ~50/s — uncached core.fsmonitor probe + per-file untracked diffs in repos with large untracked dirs #34005
Description
What version of the Codex App are you using (From "About Codex" dialog)?
26.715.31925 (5551) — Codex Framework 150.0.7871.124
What subscription do you have?
ChatGPT Pro 20x
What platform is your computer?
macOS 26.5.2 (25F84), Darwin 25.5.0, arm64 (Apple Silicon)
What issue are you seeing?
The Electron main process sustains 35–80% CPU indefinitely because the bundled VS Code git extension spawns git roughly 45–50 times per second, synchronously, on the main thread.
I traced this end-to-end. Root cause is a large untracked directory inside an opened repo — in my case a stray pnpm store (.pnpm-store/, 54,872 files, 649 MB) that was neither tracked nor gitignored.
1. sample shows the main thread blocked in process creation. ~21% of all main-thread samples sit directly in posix_spawn:
2145 __CFRunLoopRun
... node::worker::MessagePort::OnMessage
... _register_external_reference_process_wrap
689 uv_spawn + 3108
689 posix_spawn + 496
660 __posix_spawn <-- main thread, synchronous
Because the main thread is stuck in posix_spawn, it also reaps children late — ps shows a steady trail of <defunct> git processes.
2. What it actually runs. ps cannot read the argv (these processes live <1 ms), so I wrote a small native poller using proc_listpids() + sysctl(KERN_PROCARGS2). Over a 15 s capture, sampled by duty cycle:
| share of git process time | command |
|---|---|
| ~94% | git config --null --get core.fsmonitor |
| ~4% | git -c ... diff --no-ext-diff --no-textconv --no-index --raw --numstat -z -- /dev/null <file> |
| ~2% | git -c ... hash-object -- <file> |
All with CWD=<repo root>.
Two separate defects compound here:
- (a)
git config --null --get core.fsmonitoris re-forked before every single git invocation, uncached. It dominates by a wide margin — the app spends ~94% of its git time re-reading one static config key. This alone triples the process count of every git operation. - (b) Untracked files are diffed one file per process. For each untracked file the extension runs
hash-object+diff --no-indexagainst/dev/nullto compute added-line stats. With 54,872 untracked files that is ~165,000gitprocess spawns queued up — hours of work at the observed rate.
3. System-wide collateral damage. Process creation at ~50/s saturates the macOS daemons that service it. Concurrent ps aux during the incident:
opendirectoryd 27.9%
WindowServer 19.0%
runningboardd 7.1%
launchd 6.5%
notifyd 6.5%
logd 6.3%
load average: 5.19 6.01 5.08
The system PID counter wrapped through its entire range (to 99999) during a routine observation window. So the true cost is well above what Activity Monitor attributes to Codex.
4. Newly added ignore rules do not take effect until restart. After adding .pnpm-store/ to .git/info/exclude, git status --porcelain -uall dropped from 54,897 → 24 — but CPU and spawn rate were unchanged (39.5% CPU, ~40 spawns/s), because the already-enumerated file list is held in memory and keeps draining. Only a full app restart clears it.
What steps can reproduce the bug?
- Open any git repo in Codex Desktop on macOS.
- Create a large untracked directory inside it that is not in
.gitignore. A pnpm store reproduces it exactly:Any directory with tens of thousands of untracked files works.cd <repo> pnpm install --store-dir .pnpm-store # ~50k files
- Leave the app open — no prompt or task required, it happens while idle.
- Observe:
I measured 400–479 distinct git PIDs per 10 s window, consistently across many runs.
# spawn rate (replace <PID> with the ChatGPT/Codex main process) end=$((SECONDS+10)); while [ $SECONDS -lt $end ]; do \ ps -eo pid,ppid,comm | awk -v p=<PID> '$2==p && $3 ~ /git/ {print $1}'; \ done | sort -u | wc -l
sample <PID> 5confirms the main thread sitting inposix_spawn.
What is the expected behavior?
core.fsmonitorshould be read once per repo and cached, not re-forked before every git call. This is the single highest-leverage fix — it removes ~94% of git process time on its own.- Untracked-file diff stats should be batched (one
gitinvocation covering many paths) rather than 2 processes per file, and should be capped/skipped past a threshold (e.g. >5,000 untracked files), or deferred until the user actually expands that part of the SCM tree. - Process spawning must not happen synchronously on the Electron main thread — this blocks UI and delays child reaping.
- Changes to
.gitignore/.git/info/excludeshould invalidate the pending scan queue without requiring an app restart.
Additional information
Workaround (both steps required): add the directory to .gitignore and restart the app — the ignore rule alone does not stop the in-flight queue. In my case I deleted the stray store outright, which dropped untracked files to 24.
Possibly related, but I believe none identify this mechanism:
- Codex Desktop causes high CPU usage and overheating on Apple Silicon Mac #29374 — high CPU / overheating on Apple Silicon, no root cause identified. This may well be the underlying cause for that report.
- Windows 10 app 26.616.4196.0 repeatedly spawns Git root/metadata probes while idle #29110 — Windows: repeated git root/metadata probes while idle; also shows
git config --null --get core.fsmonitorin the loop, which supports defect (a) being cross-platform. - Windows Desktop leaves repeated/stuck git.exe polling processes in multi-repo workspace #29408 — Windows: repeated/stuck
git.exepolling in multi-repo workspaces. - Codex Desktop v26.527.30818 sustained main-process CPU burn after update #25159 — sustained main-process CPU burn after update.
Happy to supply the native argv-capture source or raw sample output if useful.
Activity
kkkzbh commentedon Jul 19, 2026
Additional Linux reproduction on the same Desktop build, with cancellation-path evidence.
Environment
26.715.31925Observed behavior
strace -f -c -p <main-pid>recorded 2,100execve, 350clone, and 1,406wait4calls. Theexecvetotal includes failed PATH probes; there were 350 actual child commands./usr/bin/gitexecutions, about 38.7 Git commands/second.git ... diff --no-index --raw --numstat -z -- /dev/null <untracked-path>git ... hash-object -- <untracked-path>requestKind=review-summary source=review_model.I also inspected
.vite/build/worker.jsfrom the packagedapp.asar. In this build, thereview-summarylive-query path calls the core scan function (V8(...)in the minified bundle) without threading the live query'sAbortSignalinto it. The outer cancellation helper can reject the consumer while the already-running file scan continues.This aligns with the cancellation defect described in #27570. That report uses nested repositories to trigger an unbounded hash fallback; this reproduction uses ordinary untracked generated files and reaches the per-file diff/hash path described here.
Confirmed workaround
Git tracking was unused for this directory, so I disabled repository detection by moving
.gitaside. A 6-second post-change trace recorded zero successful Git executions. Existing in-flight work still had to drain or be cleared by restarting the app, consistent with this report.Suggested fix boundary
AbortSignalthrough the core scan and every child Git operation.