What happened
Threads open slowly and the VCS/PR state in the sidebar takes 10–18s to settle. Not tied to a
particular update — it has always behaved this way on this machine. The desktop app intermittently
shows "Reconnecting" whenever anything else on the box uses CPU.
Every repository I work on is hosted on a GitHub Enterprise Cloud tenant with a data-residency
hostname of the form <tenant>.ghe.com. gh is authenticated against that host and works fine
from the terminal.
Diagnosis
A GHE host is classified unknown, can never be refined, and the failed refinement is retried on
every read without ever being cached. Verified against main @ ccf220b.
1. <tenant>.ghe.com is never detected as GitHub.
packages/shared/src/sourceControl.ts:183 matches GitHub as host === "github.com" or a github
DNS label. <tenant>.ghe.com has neither, so detectSourceControlProviderFromRemoteUrl returns
kind: "unknown".
2. Nothing can refine it. Of the five discovery specs, only GitLab
(GitLabSourceControlProvider.ts:107) and Forgejo (managed-cli) register a refineUnknownRemote.
The GitHub spec registers none. So there is no code path by which a GHE host can ever resolve to
github, no matter how many times it is probed.
3. The refinement runs uncached on every read. SourceControlProviderRegistry.ts:261 only
consults providerContextCache when context is undefined. Both hot callers pass context
explicitly, so both bypass it entirely:
PullRequestService.ts:615 (refineUnknownProjectKinds) — no caching whatsoever
server.ts:307 (identity resolver refine) — 1 min TTL
Negative results are discarded rather than cached: ForgejoSourceControlProvider.ts:150
(orElseSucceed(() => [])), SourceControlProviderDiscovery.ts:328 (orElseSucceed(() => null)),
and the registry cache uses Duration.zero on failure (SourceControlProviderRegistry.ts:258).
4. firstSuccessOf multiplies it by the number of projects on the host.
refineUnknownProjectKinds groups candidates by baseUrl and runs Effect.firstSuccessOf over
them. Every project on the same GHE host lands in one group, and because none can ever succeed, the
entire list is exhausted on every call. 11 of my 14 projects share one host.
Each candidate spawns fj, tea and glab. None are installed — these are pure failed spawns.
Measured: one PullRequestService.canonicalRef = 12 refinements → 24 listLogins → 20.8s.
PullRequestSyncReactor runs syncGroup at concurrency: 8, every 60s.
5. WSL2 makes each failed spawn ~100x more expensive. This is why it is so much worse here than
it would be on native Linux. The server's PATH contains 36 /mnt/c/... entries, so every ENOENT
lookup is a scan across the 9p filesystem. Measured on this machine, 20 spawns of a missing binary:
3.05s (152ms each) versus 0.043s (2.1ms each) with a stub resolving first on PATH.
The result is a sweep that cannot keep up with its own 60s schedule, and a server with no CPU
headroom — so any unrelated load (a test run) pushes the health endpoint past the desktop app's 10s
timeout and triggers "Reconnecting".
Steps to reproduce
- Have
gh authenticated against a GitHub Enterprise host whose hostname contains no github DNS
label — e.g. <tenant>.ghe.com, or any GHES vanity domain.
- Add several projects (I have 11) whose
origin remote points at that host.
- Do not install
fj, tea or glab.
- Open the app and watch
server.trace.ndjson.
Every PullRequestSyncReactor sweep re-probes all three CLIs once per project, forever. The effect
scales with the number of projects sharing the host, and is far more visible on WSL2 than on native
Linux or macOS because of the PATH cost above.
Version
0.0.41-nightly.20260916.1795 (t3 triage context reports installed version 0.0.42)
Environment
WSL2 (Ubuntu, kernel 6.6.87.2-microsoft-standard-WSL2) on Windows, x64, 16 cores.
Node v26.8.2. Desktop app against a local server. gh 2.x authenticated to both github.com and
the enterprise host. fj, tea, glab, az not installed.
Evidence
225s window, 4 rotated trace files, 21,847 spans
n p50 max span
1629 335.0ms 2674.6ms ForgejoCli.listLogins (874 failures, ~50%)
816 1175.8ms 4341.6ms refineUnknownRemoteProvider
815 658.4ms 2718.3ms ForgejoSourceControlProvider.refineUnknownRemote
1762 476.2ms 3641.2ms processRunner.runProcessCore
spawn failures in window: glab 881, tea 873
slowest spans:
119405ms PullRequestSyncReactor.sweep (on a 60s Schedule.spaced)
31316ms ws.rpc.vcs.refreshStatus
30127ms ws.rpc.subscribeVcsStatus
23001ms PullRequestSyncReactor.syncGroup (x8 back-to-back, concurrency: 8)
23001ms PullRequestService.canonicalRef
one canonicalRef subtree (20.8s):
12 refineUnknownRemoteProvider
12 ForgejoSourceControlProvider.refineUnknownRemote
12 ForgejoCli.readKeys
24 ForgejoCli.listLogins
24 processRunner.runProcessCore
glab spawn parentage (796 of 881):
VcsProcess.runUnbounded < VcsProcess.run < refineUnknownRemoteProvider < PullRequestService.canonicalRef
Related issues
#5087 (GHE support, closed; its PR #6052 closed unmerged) and #5089 (still open) would both remove
the root cause. This issue is about the cost of the failure mode itself: even with GHE detection
shipped, any genuinely unrecognised host still re-probes three CLIs per project per sweep forever,
because the negative result is never cached and firstSuccessOf exhausts the whole candidate group.
#11220 is adjacent — idle sweep cadence and caches that expire before the next sweep — but its root
cause is scheduling, not unrefinable provider detection. #5932 is the change that made hostname
matching deliberately strict.
Fix applied or workaround
No fix for the detection itself; the playbook rules out patching the source. As a local mitigation,
shadowed the three never-installed CLIs so the lookups stop scanning the Windows PATH:
for c in tea fj glab; do
printf '#!/bin/sh\nexit 1\n' > ~/.local/bin/$c
chmod +x ~/.local/bin/$c
done
VcsProcess.ts:167 fails on non-zero exit when allowNonZeroExit is unset, so discovery still
reports both providers as missing and refinement still returns unknown — identical behaviour,
cheaper. Measured before/after:
ForgejoCli.listLogins p50 335ms -> 187ms
canonicalRef p50 20.8s -> 10.9s
syncGroup max 23.0s -> 10.9s
Roughly 2x end-to-end — less than the isolated PATH benchmark suggests, because the spans also
include spawn overhead and 8-way contention. It is a mitigation, not a fix: the probe storm is
still there, just cheaper per probe.
Filed by
claude (opus-5) via t3 triage
What happened
Threads open slowly and the VCS/PR state in the sidebar takes 10–18s to settle. Not tied to a
particular update — it has always behaved this way on this machine. The desktop app intermittently
shows "Reconnecting" whenever anything else on the box uses CPU.
Every repository I work on is hosted on a GitHub Enterprise Cloud tenant with a data-residency
hostname of the form
<tenant>.ghe.com.ghis authenticated against that host and works finefrom the terminal.
Diagnosis
A GHE host is classified
unknown, can never be refined, and the failed refinement is retried onevery read without ever being cached. Verified against
main@ccf220b.1.
<tenant>.ghe.comis never detected as GitHub.packages/shared/src/sourceControl.ts:183matches GitHub ashost === "github.com"or agithubDNS label.
<tenant>.ghe.comhas neither, sodetectSourceControlProviderFromRemoteUrlreturnskind: "unknown".2. Nothing can refine it. Of the five discovery specs, only GitLab
(
GitLabSourceControlProvider.ts:107) and Forgejo (managed-cli) register arefineUnknownRemote.The GitHub spec registers none. So there is no code path by which a GHE host can ever resolve to
github, no matter how many times it is probed.3. The refinement runs uncached on every read.
SourceControlProviderRegistry.ts:261onlyconsults
providerContextCachewhencontextisundefined. Both hot callers passcontextexplicitly, so both bypass it entirely:
PullRequestService.ts:615(refineUnknownProjectKinds) — no caching whatsoeverserver.ts:307(identity resolverrefine) — 1 min TTLNegative results are discarded rather than cached:
ForgejoSourceControlProvider.ts:150(
orElseSucceed(() => [])),SourceControlProviderDiscovery.ts:328(orElseSucceed(() => null)),and the registry cache uses
Duration.zeroon failure (SourceControlProviderRegistry.ts:258).4.
firstSuccessOfmultiplies it by the number of projects on the host.refineUnknownProjectKindsgroups candidates bybaseUrland runsEffect.firstSuccessOfoverthem. Every project on the same GHE host lands in one group, and because none can ever succeed, the
entire list is exhausted on every call. 11 of my 14 projects share one host.
Each candidate spawns
fj,teaandglab. None are installed — these are pure failed spawns.Measured: one
PullRequestService.canonicalRef= 12 refinements → 24listLogins→ 20.8s.PullRequestSyncReactorrunssyncGroupatconcurrency: 8, every 60s.5. WSL2 makes each failed spawn ~100x more expensive. This is why it is so much worse here than
it would be on native Linux. The server's PATH contains 36
/mnt/c/...entries, so every ENOENTlookup is a scan across the 9p filesystem. Measured on this machine, 20 spawns of a missing binary:
3.05s (152ms each) versus 0.043s (2.1ms each) with a stub resolving first on PATH.
The result is a sweep that cannot keep up with its own 60s schedule, and a server with no CPU
headroom — so any unrelated load (a test run) pushes the health endpoint past the desktop app's 10s
timeout and triggers "Reconnecting".
Steps to reproduce
ghauthenticated against a GitHub Enterprise host whose hostname contains nogithubDNSlabel — e.g.
<tenant>.ghe.com, or any GHES vanity domain.originremote points at that host.fj,teaorglab.server.trace.ndjson.Every
PullRequestSyncReactorsweep re-probes all three CLIs once per project, forever. The effectscales with the number of projects sharing the host, and is far more visible on WSL2 than on native
Linux or macOS because of the PATH cost above.
Version
0.0.41-nightly.20260916.1795 (
t3 triagecontext reports installed version 0.0.42)Environment
WSL2 (Ubuntu, kernel 6.6.87.2-microsoft-standard-WSL2) on Windows, x64, 16 cores.
Node v26.8.2. Desktop app against a local server.
gh2.x authenticated to bothgithub.comandthe enterprise host.
fj,tea,glab,aznot installed.Evidence
Related issues
#5087 (GHE support, closed; its PR #6052 closed unmerged) and #5089 (still open) would both remove
the root cause. This issue is about the cost of the failure mode itself: even with GHE detection
shipped, any genuinely unrecognised host still re-probes three CLIs per project per sweep forever,
because the negative result is never cached and
firstSuccessOfexhausts the whole candidate group.#11220 is adjacent — idle sweep cadence and caches that expire before the next sweep — but its root
cause is scheduling, not unrefinable provider detection. #5932 is the change that made hostname
matching deliberately strict.
Fix applied or workaround
No fix for the detection itself; the playbook rules out patching the source. As a local mitigation,
shadowed the three never-installed CLIs so the lookups stop scanning the Windows PATH:
VcsProcess.ts:167fails on non-zero exit whenallowNonZeroExitis unset, so discovery stillreports both providers as missing and refinement still returns
unknown— identical behaviour,cheaper. Measured before/after:
Roughly 2x end-to-end — less than the isolated PATH benchmark suggests, because the spans also
include spawn overhead and 8-way contention. It is a mitigation, not a fix: the probe storm is
still there, just cheaper per probe.
Filed by
claude (opus-5) via
t3 triage