Thread PR discovery and settlement sweeps run every minute without client demand, and their caches expire before the next sweep
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 23k
- Forks
- 5.9k
- Avg merge
- 11h 14m
- Merged PRs (30d)
- 357
Description
What happened
Desktop app on Windows, 12 projects in the sidebar, left idle overnight with the window minimized and no client interacting.
The server kept spawning git and gh continuously.
Over 13 h of trace logs the background pass ran every ~68 s without a single gap, each pass lasting ~8 s.
OS-level process tracing attributed roughly 170 process creations per pass to the server (Git for Windows launcher + real git.exe + conhost.exe per command, gh, taskkill), on the order of 170k–190k process creations per day.
Load grows with the number of projects and with the number of threads that have a branch or a PR.
Diagnosis
- Two background loops repeat on
Schedule.spaced("1 minute"), i.e. 60 s after the previous pass ends:ThreadPullRequestReactor(apps/server/src/orchestration/ThreadPullRequestReactor.ts:353-362, comment: "Run without client demand") andThreadSettlementReactor(ThreadSettlementReactor.ts:263). Neither checks connected clients, window focus, or whether any input changed. A third one,PullRequestSyncReactor, also ticks every minute but only re-reads keys that are due (15 min), so it is cheap. - Since #10424 the settlement sweep returns early when both auto-settle settings are off. The PR discovery sweep has no gate at all. With auto-settle disabled the server still runs a full discovery pass every minute; in my trace that is 3–8 s and a few dozen process spawns per pass on 12 projects.
- The caches are sized to the cadence, so they never carry a value across passes.
PR_LOOKUP_CACHE_TTLis 60 s (GitManager.ts:138; the comment says it deliberately matches the settlement sweep), and the positive TTL ofRepositoryIdentityResolveris 60 s (RepositoryIdentityResolver.ts:16). The next pass starts at 60 s plus the previous pass duration, when every entry has expired. The caches only dedupe within one pass. - Within one pass the same work still repeats.
branchPullRequestruns 2–3 times per thread group (discovery, settlement, and settlement again withrefresh: true). Its preparatorygit remote,for-each-ref, andsymbolic-refcalls are not cached.readConfigValue(git config --get) is not cached either, and bothresolvePrLookupRepositoryIdentityandresolveBranchHeadContextresolve the head remote andoriginin parallel (GitManager.ts:1293-1299,1323-1329), which is the same remote whenever the branch tracksorigin. Result: 6–8 identicalgit config --get remote.origin.urlper pass and per thread. - Threads whose linked PR is merged or closed are re-checked every minute around the cache: settlement calls
branchPullRequest(..., { refresh: true })(ThreadSettlementReactor.ts:185-191), which drops the cache entry and runsghagain. A thread that had activity after its PR merged never settles, so this repeats for days. One thread in my install has been re-checked every minute since the merge. - Projects that are not git repositories are re-probed on every pass because the repository root cache stores a
nullresult with TTL zero (RepositoryIdentityResolver.ts:149-152). Details in #8949. - Still present on
main@ 211618fd9f (2026-09-10): both one-minute loops, both 60 s TTLs, the zero negative TTL, the parallel duplicategit configreads. #10839 stopped polling auto-closed threads. #11007 madesummary()callrequireProjectbefore its cache check, so even a cache hit now costs a full identity pass.
Steps to reproduce
- Run the desktop app (or
t3 serve) with about ten git projects that have a GitHub remote, a few threads with a branch, and at least one thread whose PR is already merged. - Set
sidebarAutoSettleOnMerge: falseandsidebarAutoSettleAfterDays: nullto rule out #9714. - Disconnect every client or minimize the window and leave the machine for an hour.
- Count
ThreadPullRequestReactor.synchronizeandprocessRunner.runProcessCorespans inserver.trace.ndjson, or trace process creation at the OS level.
Expected: no git or gh work while nothing is connected, or a bounded backoff; cache lifetimes that outlive the cadence so an unchanged branch costs nothing.
Actual: a full pass every 60 s plus pass duration, dozens of processes per pass, forever.
Version
0.0.41-nightly.20260908.1414 (desktop). Source references checked against main @ 211618fd9f.
Environment
Windows 11 Pro 26200, desktop app hosting the server, git 2.55.0.windows.5 (Git for Windows), gh 2.100.0. 12 projects, threads with branches and PRs spread across them.
Evidence
Span counts from one rotated trace file, 76 minutes, auto-settle still enabled:
ThreadPullRequestReactor.synchronize 67 ~8 s each, one every ~68 s
ThreadSettlementReactor.sweep 67 ~8 s each
processRunner.runProcessCore 2345 ~35 spawns per pass
runGitCommand 1407
RepositoryIdentityResolver.resolve 2412 ~36 per pass, 3 per project
GitVcsDriver.readConfigValue 804
branchPullRequest 201 3 per pass
Consecutive passes: synchronize at 04:31:06 (8068 ms), 04:32:14 (7870 ms), 04:33:22 (8397 ms).
Same install after disabling both auto-settle settings, 96 minutes: ThreadSettlementReactor.sweep now 0 ms, ThreadPullRequestReactor.synchronize still 3–8 s every ~68 s, 247 process spawns.
Related issues
- #9714 / #10424: gated the settlement sweep on the settings. This report is what remains: the discovery sweep has no gate, and both sweeps' caches are shorter than the cadence.
- #2537 / #10818: per-spawn cost on Windows (console, taskkill). This report is about how many spawns the server schedules, which is platform-independent.
- #8949: identity resolver caching gaps. Sweep-specific numbers added there.
- #5722, #7076: client-driven VCS work. This work runs with zero clients.
- #9170: same sweep, unknown-provider logging.
Fix applied or workaround
Disabled both auto-settle settings. That stops the settlement sweep only. There is no setting for the PR discovery sweep.
Filed by
Claude Code (Claude Fable 5.1), following the t3 triage playbook from a checkout of main.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with ThreadPullRequestReactor.ts:353-362 and ThreadSettlementReactor.ts:263, then inspect PR_LOOKUP_CACHE_TTL in GitManager.ts, RepositoryIdentityResolver.ts:16 and 149-152, and the cited branch lookup paths. Use the reproduction steps and trace spans to verify idle clients do not trigger unbounded work, unchanged results survive the sweep cadence, and duplicate process activity is reduced.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- git, github, typescript
- Domain
- backend, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 50/100