Codex Desktop (macOS): source-control watcher spawns thousands of git/sec on workspaces containing embedded git repos → syspolicyd/trustd CPU storm, fans max
Summary
On macOS, the Codex desktop app's source-control integration runs git status continuously (file-watcher driven). When the open workspace contains embedded/nested git repositories (other repos cloned into a tracked parent repo — recorded as gitlinks, not proper submodules), the watcher recurses into every embedded repo and spawns thousands of short-lived git processes per second (≈4000/s with 18 embedded repos here). On macOS each git exec triggers Gatekeeper code-signature validation, so syspolicyd (~180%) and trustd (~100%) peg the CPU — fans max, system-wide beach-balls — while Codex's own process CPU stays low, which makes it look like a "GPU/rendering" problem.
Smoking gun / negative controls:
- The
codexCLI is unaffected (no continuous SCM watcher) → it's the desktop watcher, not git or the model. - A fresh/empty workspace is fine; only workspaces containing embedded repos trigger it.
git config diff.ignoreSubmodules allon the workspace drops git to 0/s and syspolicyd to ~5% → confirms the recursion-into-embedded-repos path is the cause.
Environment
| | |
|---|---|
| Codex Desktop | observed on 26.611.62324; since auto-updated to 26.616.32156 (repro is version-agnostic) |
| Platform | macOS 26.5.1 (25F80), Apple Silicon — Darwin 25.5.0 arm64 |
| Display | Built-in Liquid Retina XDR (ProMotion 120 Hz) |
| Model / Plan | gpt-5.5 / ChatGPT subscription (official login) |
| git | 2.50.1 (Apple Git-155) |
| Workspace | 41 GB repo with 18 embedded git repos (gitlinks, mode 160000, no .gitmodules) |
Steps to reproduce (minimal, version-agnostic)
# 1) A git repo that contains a few embedded git repos
D=/tmp/codex-nested-demo; mkdir -p "$D"; git -C "$D" init -q
echo root > "$D/root.txt"; git -C "$D" add . && git -C "$D" commit -qm init
for d in nested-a nested-b nested-c; do
mkdir -p "$D/$d"; git -C "$D/$d" init -q; echo x > "$D/$d/x.txt"
git -C "$D/$d" add . && git -C "$D/$d" commit -qm init
git -C "$D" add "$d" # git warns: "adding embedded git repository" — creates the gitlink
done
git -C "$D" commit -qm "add nested repos"
# 2) Open /tmp/codex-nested-demo as the workspace in Codex Desktop.
# 3) Watch git spawn rate from any terminal:
for i in $(seq 1 10); do echo "git procs: $(pgrep -x git | wc -l)"; sleep 0.3; done
# -> stays continuously high (thousands/s with more embedded repos)
# 4) macOS `top`: syspolicyd ~180%, trustd ~100%; fans ramp.
Real-world trigger: this happens naturally when you git clone projects into a folder that is itself a git repo — git records each as a gitlink (see its own warning above).
Expected vs actual
- Expected: the SCM watcher polls
git statusat a bounded rate and does not dive into every embedded repo; CPU near-idle when not editing. - Actual: unbounded
gitspawning (~4000/s) → syspolicyd/trustd CPU storm, fans, global lag.
Impact
Opening any large / monorepo-style workspace that contains nested git repos (common: a projects folder, vendored clones, tool checkouts) causes sustained 100%+ system CPU, thermal throttling, battery drain, and beach-balls across all apps — easily misread as a GPU issue since the Codex process itself looks idle.
Local diagnostics
pgrep -x git | wc -l→ ~4000/s;lsof -d cwd <git-pid>→ all running in the workspace root.top: syspolicyd ~180%, trustd ~100%, WindowServer high; quitting Codex → ~0./usr/bin/sample <Codex pid>→ main thread in V8/JS +uv__fsevents(libuv file-watch).- A single
git statuson the workspace is ~0.18 s → the problem is spawn rate × N embedded repos, not slow git.
Root-cause hypothesis
The desktop SCM watcher re-runs git status (incl. per-submodule status) on a tight, file-event-driven loop with no debounce and no embedded-repo/depth guard. With N embedded gitlinks it spawns ≥N git children each pass; on macOS the per-exec Gatekeeper validation turns that into a syspolicyd/trustd storm.
Workaround (confirmed)
On the workspace repo: git config diff.ignoreSubmodules all (+ git config core.untrackedCache true) → git 0/s, syspolicyd ~5%. Cleaner structural fix: untrack the embedded repos (git rm --cached <dir>) and .gitignore them so they stay independent repos the parent ignores.
Suggested direction
- Debounce/coalesce the SCM
git statuspolling; cap concurrentgitchildren. - Don't recurse into embedded/nested repos for status; honor
diff.ignoreSubmodules. - Add a scan-depth / exclude setting (prior art: VS Code
git.repositoryScanMaxDepth).
Related (searched, appears distinct)
#27570 / #27382 (nested repos, different code paths) and #11524 (macOS git→syspolicyd, venv trigger) — filing the macOS desktop SCM-watcher + embedded-repo + syspolicyd combination as a distinct, instrumented report.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗