Skip to content

Supacode worktree refresh creates gh process storms and 200+ load averages #830

Description

@realgenekim

What happened?

When I use Supacode with 17 repositories and 105 unarchived worktrees, I expected background GitHub refreshes to unnoticeable, but instead Supacode appears to launch large concurrent bursts of gh, git, and shell processes, contributing to load-average spikes above 200—and sometimes 400—on my MacBook Air.

Issue #783 fixes almost all of the issues, but I propose another patch that would cap the number of polling processes to ameliorate the effects of slow networks, large numbers of worktrees/branches, and long launch-time fan-out.

Personal letter follows, then full report:

I'm so much enjoying Supacode and the ability to use ZMX sessions -- I'm using it all the time, and look forward to seeing where you are taking this fantastic tool.

I've routinely started having load average spikes to 200+ (sometimes even 400+) on my macOS MacBook Air (24GB RAM). I suspect it might be because of the 17 repositories and 105 unarchived worktrees I have on my system (please don't judge. Hahaha.)

I finally was able to get enough telemetry to isolate it to 64 gh CLI commands, followed by git, bash, and maybe others.

If I could wave a magic wand, these processes would be throttled so it never creates such huge simultaneous load on my system.

The rest of this is written by Codex Sol -- I hope this helps!

===

Summary

Supacode appears to launch bursts of short-lived gh, git, and shell processes while refreshing GitHub or remote-branch state. With 17 repositories and 105 unarchived worktrees, the work may scale with the number of worktrees. If refresh sweeps overlap, the instantaneous process count can grow even faster.

This is not yet proof of the exact scaling function or refresh path. It is direct evidence that one Supacode process spawned a large Git/GitHub/shell burst, plus a separate 30-minute recording that observed 64 gh-family process births during a severe load event.

Impact

  • The laptop becomes severely sluggish or briefly unusable.
  • One-minute load average routinely exceeds 200 and has sometimes exceeded 400.
  • During one instrumented event, load reached 206.58 on an eight-core machine, with 193 runnable processes at the observed peak.
  • During another recorded event, one-minute load reached 280.95, or 35.12 runnable jobs per logical CPU.
  • The bursts can create memory and swap pressure as many short-lived processes and their helpers overlap.
  • Supacode is most valuable when many repositories and concurrent worktrees are open, so reducing the number of worktrees works against the reason I use the product.

Environment

  • Supacode: 0.10.8 (build 1785775286)
  • Computer: MacBook Air, model Mac14,15
  • Chip: Apple M2
  • CPU: 8 cores (4 performance and 4 efficiency)
  • Memory: 24 GB
  • OS: macOS 26.5.2, build 25F84
  • GitHub CLI: 2.70.0
  • Git: Apple Git 2.50.1
  • Supacode GitHub integration: enabled
  • Supacode inventory at investigation time: 17 repositories and 105 unarchived worktrees

Steps to reproduce or investigate

I do not yet have a deterministic single-click reproduction, but this workload makes the event recur:

  1. Enable Supacode's GitHub integration.
  2. Add many repositories and retain many unarchived worktrees. My current inventory is 17 repositories and 105 unarchived worktrees.
  3. Leave Supacode running long enough for remote-branch, pull-request, or worktree refreshes to occur.
  4. Observe process births and parent/child relationships over time, rather than relying only on a single top or Activity Monitor snapshot. Most affected children are too short-lived to diagnose reliably from a snapshot.
  5. Look for bursts of gh, git, bash, and other shell helpers whose direct parent is Supacode.
  6. Compare the burst timing with load average, runnable-process count, and memory pressure.

The issue may be easier to reproduce with many worktrees whose branches have GitHub remotes or pull requests.

Expected behavior

Background GitHub and worktree refresh should remain bounded regardless of how many repositories or worktrees are registered. It should not create enough simultaneous subprocesses to materially affect interactive use of the machine.

Actual behavior

Periodic bursts create many short-lived GitHub CLI, Git, and shell processes. These can coincide with hundreds of runnable processes and extremely high system load.

Evidence

Capture A: direct Supacode child-process lineage

On August 24, a bounded 15.602-second sample recorded:

  • system load average: 206.58, 78.86, 40.25;
  • eight logical CPUs, making the one-minute normalized load 25.82 per CPU;
  • 193 runnable processes at the observed peak;
  • 209 process births and 247 exits in the sample;
  • 176 processes that were both born and exited within the short sample; and
  • about 30.1 GB of swap in use on the 24 GB machine.

The Supacode process, whose CWD was /, directly spawned:

  • 49 children in 15.602 seconds;
  • 44 children that exited during the same window;
  • 26 children concurrently at the observed peak;
  • 13 gh-family children;
  • 14 git-family children;
  • 17 bash children; and
  • three zsh plus two env children.

The observed child birth rate from Supacode alone was 3.14 processes per second. Because this was sampling rather than kernel-level process tracing, these counts are lower bounds.

This capture establishes direct Supacode parentage for the GitHub/Git/shell burst. It does not merely infer parentage from processes happening to exist at the same time.

Capture B: 30-minute flight-recorder window

A separate 30-minute history on August 25 recorded:

  • peak one-minute load of 280.95;
  • normalized peak load of 35.12 per logical CPU;
  • 64 gh-family births: 46 observed as gh and 18 already represented as exited (gh) processes;
  • 18 exited (bash) births and 16 shell births; and
  • substantial unrelated activity, including Spotlight workers and browser processes.

The second capture is consistent with the same process-storm failure mode, but it does not independently prove that every process in the window came from Supacode. The direct-lineage capture above is the stronger causal evidence.

Python processes were also present in the larger incident history. I have not established that Supacode launched them; at least one later Python burst had a different parent application. I would therefore treat Python as a question to investigate, not as an allegation about Supacode.

Working hypothesis

The behavior looks consistent with a refresh design whose cost resembles:

background cost ~= worktrees
                   x Git/GitHub calls per worktree
                   x concurrently overlapping refresh sweeps

The likely sequence is:

refresh tick
    |
    +-- enumerate repositories/worktrees
    +-- refresh remote branch or pull-request state
    +-- launch gh, git, and shell helpers
            x many worktrees

The installed Supacode binary also contains readable diagnostic names such as Worktree refresh loop, pollRemoteBranch, remoteHeadPollTasks, pullRequestTasks, and repositoryPullRequestRefresh. Those names are clues, not proof of which path caused this particular event.

The important independent variable may be one of the following:

  1. all 105 unarchived worktrees;
  2. only the approximately 31 worktrees represented in stored/open layouts;
  3. the 17 repositories, with several GitHub calls per repository; or
  4. one of those counts multiplied by overlapping or superseded refresh jobs.

Raw Git branches that Supacode does not know about probably do not matter. Supacode-known worktrees, their visibility/archive state, and their GitHub tracking state are more plausible drivers.

Magic-wand mitigations

Any combination of these would help. The first four seem likely to provide the largest benefit:

  1. Use a global subprocess concurrency limit. Cap background gh and git work at perhaps two concurrent jobs, independent of worktree count.
  2. Make refresh single-flight. Never start a new repository or worktree refresh while an equivalent refresh remains active. Coalesce new requests into the in-flight job.
  3. Fetch GitHub state once per repository. Query branches and pull requests once, cache that snapshot briefly, and map the results to all of the repository's worktrees in memory.
  4. Cancel superseded work completely. When a refresh is replaced or a surface closes, terminate its entire child process group so shell and authentication helpers do not survive.
  5. Skip inactive worktrees. Avoid background remote/PR polling for archived, hidden, stale, or otherwise inactive worktrees. Refresh them lazily when selected.
  6. Add a TTL cache. Reuse recent GitHub results across worktrees and UI surfaces rather than launching duplicate CLI requests.
  7. Back off and add jitter. Increase refresh intervals after failures or inactivity, and distribute repository refreshes over time instead of beginning them simultaneously.
  8. Adapt to host pressure. Pause or slow background refresh when macOS reports high load, memory pressure, thermal pressure, or a large runnable queue.
  9. Offer a low-impact mode. Let users choose active-worktree-only, manual, battery-conscious, or configurable refresh behavior.
  10. Expose refresh diagnostics. A small status view showing queued/running/skipped refreshes, subprocess concurrency, last successful refresh, and next scheduled refresh would make field diagnosis much easier.

Suggested acceptance criteria

  • With at least 100 registered unarchived worktrees, an idle Supacode instance never exceeds the configured background gh/git concurrency cap.
  • Repeated refresh triggers for the same repository coalesce into one in-flight operation.
  • GitHub CLI traffic scales primarily with repositories or changed state, rather than multiplying independently by every worktree and UI surface.
  • Closing or archiving a worktree cancels any associated background operation and its descendants.
  • An idle refresh cycle does not materially elevate load average or impair foreground interaction.
  • A diagnostic log or status screen makes it possible to attribute each gh invocation to its repository, worktree, refresh reason, and parent refresh transaction.

A controlled experiment that could confirm the scaling behavior

  1. Record gh/git births and peak concurrency over two refresh windows with the current 105 unarchived worktrees.
  2. Archive a known batch of stale worktrees without changing the number of repositories.
  3. Repeat the same measurement.
  4. Compare subprocess births and peak concurrency against the worktree-count reduction.
  5. Separately reduce the repository count while holding worktrees per repository approximately constant.

That should distinguish O(worktrees) behavior from O(repositories), per-visible-layout behavior, and overlapping-refresh behavior.

Current workarounds

  • Archive old worktrees aggressively <-- I've never actually done this. I'm going through pruning process right now -- painful!!!
  • Quit Supacode during a severe storm and relaunch after load subsides.

These workarounds sacrifice useful Supacode functionality. A bounded scheduler inside Supacode would be much safer and would preserve the multi-repository, multi-worktree workflow that makes the tool so valuable.

Questions for the Supacode team

  • Is remote/PR refresh scheduled per repository, per worktree, per visible surface, or some combination?
  • Can a refresh timer fire again before the previous sweep has completed?
  • Do multiple worktrees from the same repository share a GitHub response cache?
  • Is there currently a global concurrency limit for gh and git children?
  • When a task is canceled, does Supacode terminate the whole child process group?
  • Would additional flight-recorder output, timestamps, or a controlled worktree-count experiment be useful?

I am happy to gather another bounded trace or run a controlled A/B test if you can suggest the most useful instrumentation.

Steps to reproduce

Steps to reproduce or investigate

I do not yet have a deterministic single-click reproduction, but this workload makes the event recur:

  1. Enable Supacode's GitHub integration.
  2. Add many repositories and retain many unarchived worktrees. My current inventory is 17 repositories and 105 unarchived worktrees.
  3. Leave Supacode running long enough for remote-branch, pull-request, or worktree refreshes to occur.
  4. Observe process births and parent/child relationships over time, rather than relying only on a single top or Activity Monitor snapshot. Most affected children are too short-lived to diagnose reliably from a snapshot.
  5. Look for bursts of gh, git, bash, and other shell helpers whose direct parent is Supacode.
  6. Compare the burst timing with load average, runnable-process count, and memory pressure.

The issue may be easier to reproduce with many worktrees whose branches have GitHub remotes or pull requests.

Supacode version and build

Supacode: 0.10.8 (build 1785775286)

macOS version

macOS 26.5.2, build 25F84

System locale

en_US

Mac hardware

Computer: MacBook Air, model Mac14,15 - Chip: Apple M2 - CPU: 8 cores (4 performance and 4 efficiency) - Memory: 24 GB

Relevant logs or screenshots

Relevant logs / telemetry excerpts

Capture A — direct Supacode parent/child lineage
Timestamp: 2026-08-24T21:13:37.904474Z
Sampling window: 15.602 seconds

system.load_average_1m_5m_15m = [206.58, 78.86, 40.25]
system.logical_cpus = 8
system.normalized_load_per_cpu_1m_5m_15m = [25.82, 9.86, 5.03]
system.peak_runnable_processes = 193
system.swap = total 31744 MB, used 30104 MB, free 1640 MB

all_processes.births = 209
all_processes.exits = 247
all_processes.born_and_exited_inside_window = 176
all_processes.birth_rate_per_second = 13.40

lineage.evidence = direct-ppid-lineage; observed births are a sampling lower bound
lineage.parent_command = /Applications/supacode.app/Contents/MacOS/supacode
lineage.parent_pid = 53376
lineage.parent_cwd = /
lineage.children_born = 49
lineage.children_exited = 44
lineage.peak_concurrent_children = 26
lineage.child_birth_rate_per_second = 3.14
lineage.peak_children_rss_mib = 170.4
lineage.child_families = {
  gh: 9,
  (gh): 4,
  git: 14,
  bash: 15,
  (bash): 2,
  zsh: 3,
  env: 2
}

Interpretation: during this 15.602-second sample, the Supacode process directly
spawned 49 children. Thirteen were gh-family processes, fourteen were git-family
processes, and seventeen were bash-family processes. This is direct parentage,
not merely a list of unrelated processes running at the same time.

Capture B — 30-minute process-history window
Window: 2026-08-25T21:58:39.255017Z through 2026-08-25T22:27:43.697418Z

observed_process_births.gh = 46
observed_process_births.(gh) = 18
observed_process_births.gh_family_total = 64
observed_process_births.(bash) = 18
observed_process_births.shell = 16

Immediately before that retained history window:

timestamp = 2026-08-25T21:57:39.068645Z
load_average_1m_5m_15m = [280.9502, 180.8618, 91.0449]
normalized_one_minute_load_per_cpu = 35.119
process_probe = skipped-critical-load

The process probe deliberately skipped during the 280.95 peak to avoid adding
more load to an already overloaded host. The following history window still
observed 64 gh-family births. Capture A supplies the stronger causal evidence
that Supacode directly launches this gh/git/shell family of bursts.

Important limitations

- The monitor samples once per minute, so short-lived process counts are lower
  bounds rather than a complete syscall-level trace.
- The larger window also contained unrelated Spotlight and browser activity.
- Python processes were observed, but Supacode parentage was not established;
  they should not presently be attributed to Supacode.
- These excerpts substantiate load averages above 200. The reported 400+ peaks
  were observed interactively but are not claimed as proven by these excerpts.

Are you planning to fix this yourself?

  • I intend to open a pull request once this is confirmed and marked ready.

Before submitting

  • I searched existing issues and this is not a duplicate.
  • I am on the latest available version of Supacode.
  • I understand pull requests are closed until the issue is marked ready.
  • I agree to follow this project's Code of Conduct.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions