Codex Desktop `review-summary` spawns thousands of `git hash-object` processes when nested repositories are present

Open 💬 4 comments Opened Jun 11, 2026 by 0xdevalias

Feedback ID / Bug Report ID: 019eb57d-d318-78f2-8c45-76dea130046d

What version of the Codex App are you using (From “About Codex” dialog)?

Version 26.608.12217 • Released Jun 9, 2026

What subscription do you have?

Irrelevant

What platform is your computer?

Darwin 25.3.0 arm64 arm

What issue are you seeing?

Codex Desktop can spawn thousands of concurrent git hash-object processes while loading repository review state for a thread.

In one captured reproduction, the main Codex GUI process spawned exactly 5,042 direct children over approximately 14 seconds. Every child used this command shape:

git -c core.hooksPath=/dev/null -c core.fsmonitor= \
  hash-object --no-filters -- <PATH>

All 5,042 children exited and temporarily remained as zombies under the main Codex process:

18:36:43  Codex zombies:   20
18:36:48  Codex zombies: 1,403
18:36:53  Codex zombies: 3,412
18:36:58  Codex zombies: 5,042
18:37:00  Codex zombies: 5,042
18:37:01  Codex zombies:     0

This temporarily consumed almost half of the macOS per-user process limit of 10,666.

I inspected the installed Desktop app.asar. The packaged Git worker's review-summary path:

  1. Collects paths requiring content revisions.
  2. Attempts one bulk git hash-object --no-filters -- <all paths> command.
  3. If that command fails or returns the wrong number of hashes, falls back to the equivalent of:
await Promise.all(
  paths.map(path => hashObject([path]))
);

The fallback has no concurrency limit.

I reconstructed the original argument set from the process trace:

unique paths:       5,042
regular files:      5,039
directories:            3
missing paths:           0
bulk exit status:      128
stdout hashes:           24
stderr:            fatal: Unable to hash (null)

The three directories were untracked nested Git repositories containing their own .git metadata. Each independently caused git hash-object to exit 128.

Therefore, three unhashable entries caused the application to retry all 5,042 paths concurrently.

The hashing path also does not pass the outer review request's abort signal into the Git helper. Switching away from the thread does not cancel the fallback once it starts.

What steps can reproduce the bug?

Observed reproduction:

  1. Open an outer Git repository containing:
  • thousands of visible untracked/generated files; and
  • one or more untracked nested Git repositories, each containing its own .git directory.
  1. Open a Codex thread associated with that repository.
  2. Switch away from and back to the thread, causing Desktop to refresh its repository review summary.
  3. If necessary, open a second Codex window and switch among threads associated with the repository.
  4. Monitor direct zombie children of the main Codex process:
while true; do
  CODEX_PID="$(
    pgrep -f '/Applications/Codex.app/Contents/MacOS/Codex' |
      head -1
  )"

  printf "%s  " "$(date '+%Y-%m-%d %H:%M:%S')"

  ps -axo ppid=,stat=,user= |
    awk -v pid="$CODEX_PID" -v user="$USER" '
      $2 ~ /^Z/ { total_zombies++ }
      $1 == pid && $2 ~ /^Z/ { codex_zombies++ }
      $3 == user { user_processes++ }
      END {
        printf "pid=%s  codex_zombies=%d  total_zombies=%d  user_processes=%d\n",
          pid,
          codex_zombies + 0,
          total_zombies + 0,
          user_processes + 0
      }
    '

  sleep 1
done

For prospective process identification:

OUT="$HOME/Desktop/codex-process-events-$(date '+%Y%m%d-%H%M%S').jsonl"

sudo /usr/bin/eslogger \
  --select /Applications/Codex.app \
  --select "$HOME/.codex" \
  exec fork exit \
  > "$OUT"

The JSONL contains local paths and command arguments and must be sanitized before sharing.

Two windows are not known to be required. They made the trigger easier to reproduce in this capture.

No session ID, token-limit usage, or context-window usage appears applicable because this is a Desktop Git-worker/repository-state operation.

What is the expected behavior?

Repository review-summary calculation should not create one concurrent child process per path.

Codex Desktop should:

  • recognize nested-repository/gitlink entries and avoid passing directory paths to blob hashing;
  • batch or otherwise bound git hash-object operations;
  • avoid an all-path concurrent fallback when one item fails;
  • pass request cancellation through to all Git operations;
  • serialize or deduplicate overlapping review-summary work for the same repository;
  • degrade gracefully when a repository contains a very large visible untracked tree.

A malformed or unhashable path should affect only that path, not cause thousands of subprocesses to be launched.

Additional information

Installed bundle details:

Desktop app:       26.608.12217 (build 3722)
Bundled backend:   codex-cli 0.138.0-alpha.7
app.asar SHA-256:  4545a5b9cc1e244254f444a611f21c49f4f997cb158bab7a2d7397f6954079df

The relevant packaged code is in .vite/build/worker.js.

Minified call path:

review-summary
  -> E5 / e5 / r5
  -> X8
  -> a5
  -> c5 / h5
  -> b5 / x5

Relevant identifiers:

b5 / x5   bulk hash plus unbounded single-path fallback
h5        revision calculation caller
I8 / R8   temporary-index staging using 1,024-path chunks
U8 / W8   working-tree tree construction
jQ / MQ   child_process.spawn and close-event handling

The same worker already stages untracked paths sequentially in chunks of 1,024 while constructing its temporary Git index. The process trace showed those batches, followed by write-tree, three metadata diffs, and then the 5,042-process hash fallback.

Related issues:

  • #25744
  • main-GUI-owned zombie accumulation and process-table exhaustion
  • #27382
  • Git-worker failures with nested repositories and large directory trees
  • #20933
  • passive review-summary Git fanout in large/untracked repositories
  • #18099
  • nested repositories breaking review-pane behavior

This report identifies a narrower, deterministic implementation defect than those existing reports.

Previous process-exhaustion evidence:

Raw process traces contain local paths and are not attached publicly. Sanitized extracts can be provided if needed.

See Also

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗