pingdotgg / pingdotgg/t3code

Thread PR discovery and settlement sweeps run every minute without client demand, and their caches expire before the next sweep

Open
#11,220 3 comments 0 reactions 0 assignees View on GitHub

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") and ThreadSettlementReactor (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_TTL is 60 s (GitManager.ts:138; the comment says it deliberately matches the settlement sweep), and the positive TTL of RepositoryIdentityResolver is 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. branchPullRequest runs 2–3 times per thread group (discovery, settlement, and settlement again with refresh: true). Its preparatory git remote, for-each-ref, and symbolic-ref calls are not cached. readConfigValue (git config --get) is not cached either, and both resolvePrLookupRepositoryIdentity and resolveBranchHeadContext resolve the head remote and origin in parallel (GitManager.ts:1293-1299, 1323-1329), which is the same remote whenever the branch tracks origin. Result: 6–8 identical git config --get remote.origin.url per 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 runs gh again. 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 null result 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 duplicate git config reads. #10839 stopped polling auto-closed threads. #11007 made summary() call requireProject before its cache check, so even a cache hit now costs a full identity pass.
Steps to reproduce
  1. 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.
  2. Set sidebarAutoSettleOnMerge: false and sidebarAutoSettleAfterDays: null to rule out #9714.
  3. Disconnect every client or minimize the window and leave the machine for an hour.
  4. Count ThreadPullRequestReactor.synchronize and processRunner.runProcessCore spans in server.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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.