Same shape as #748 (just-fixed via #775), one layer up.
subscribeGitHubPr in packages/integrations/github/src/resolve.ts:131 is called once per terminal from packages/server/src/meta/github.ts:35. Each call installs its own 30-second polling setInterval and spawns its own gh pr view subprocess on every poll tick and on every setGit branch change.
Why this is materially worse than the HEAD watcher case
|
HEAD watcher (#748, fixed in #775) |
GitHub PR (this issue) |
| Per-terminal cost |
one fs.watch handle |
one setInterval + one gh pr view subprocess every 30s |
| Cost on event |
one debounced callback |
full Node CLI startup + 5s timeout budget per terminal |
| N=2 terminals on same scope |
2x callback fan-out |
2x concurrent subprocess spawns every 30s |
Two terminals open in the same repo on the same branch = two gh pr view invocations every 30 seconds. Branches with PRs that haven't changed in days still pay this cost forever.
Fix shape
Refcount the subscription keyed by (repoRoot, branch). First subscriber installs the setInterval + initial gh pr view; subscribers join the listener set; last unsubscribe clears the interval and drops the registry entry.
The (repoRoot, branch) key handles the realistic case (two terminals in the same repo on the same branch) and naturally splits when one terminal switches branches (separate registry entry until the other catches up). Worktrees with the same branch but different repoRoot get independent entries — correct, since gh pr view resolves through the worktree's own remote tracking.
The pattern is the same as packages/integrations/git/src/head-watcher.ts (refcounted singleton with subscribe(onChange) → unsubscribe), just keyed differently. Should pair with the lifecycle logging requested in #778.
Out of scope for #775
/do on this same scope would have grown #775 unboundedly. Doing it as a follow-up keeps that PR auditable. Filing now so the perf hygiene rule (integration-perf-hygiene in .agency/code-police.md — "Directory watchers must be shared") gets a github-PR-flavored bullet too.
Same shape as #748 (just-fixed via #775), one layer up.
subscribeGitHubPrinpackages/integrations/github/src/resolve.ts:131is called once per terminal frompackages/server/src/meta/github.ts:35. Each call installs its own 30-second pollingsetIntervaland spawns its owngh pr viewsubprocess on every poll tick and on everysetGitbranch change.Why this is materially worse than the HEAD watcher case
fs.watchhandlesetInterval+ onegh pr viewsubprocess every 30sTwo terminals open in the same repo on the same branch = two
gh pr viewinvocations every 30 seconds. Branches with PRs that haven't changed in days still pay this cost forever.Fix shape
Refcount the subscription keyed by
(repoRoot, branch). First subscriber installs thesetInterval+ initialgh pr view; subscribers join the listener set; last unsubscribe clears the interval and drops the registry entry.The
(repoRoot, branch)key handles the realistic case (two terminals in the same repo on the same branch) and naturally splits when one terminal switches branches (separate registry entry until the other catches up). Worktrees with the same branch but differentrepoRootget independent entries — correct, sincegh pr viewresolves through the worktree's own remote tracking.The pattern is the same as
packages/integrations/git/src/head-watcher.ts(refcounted singleton withsubscribe(onChange) → unsubscribe), just keyed differently. Should pair with the lifecycle logging requested in #778.Out of scope for #775
/doon this same scope would have grown #775 unboundedly. Doing it as a follow-up keeps that PR auditable. Filing now so the perf hygiene rule (integration-perf-hygienein.agency/code-police.md— "Directory watchers must be shared") gets a github-PR-flavored bullet too.