perf(agent-manager): batch PR status lookups into one GraphQL request per sync - #14116
Conversation
… per sync A full sync resolved each worktree with 2 to 3 `gh` calls, and GraphQL is POST so ETag revalidation cannot reduce that cost. Resolve every worktree in one `gh api graphql` request per 10 worktrees instead, then hand the results to the per-worktree fetch so it skips its own lookups. Merged PRs keep their badge, forks that only share a branch name are not attributed to a worktree, and anything the batch cannot decide falls back to the existing per-worktree path unchanged.
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Files Reviewed (2 files)
The previous SUGGESTION (wasted No memory leaks found: the added guard only shortens the loop and retains nothing new across poll cycles. Previous Review Summaries (2 snapshots, latest commit f99f82a)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit f99f82a)Status: 1 Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)SUGGESTION
Files Reviewed (3 files)
The previously reported WARNING (unhandled No memory leaks found: the seed map, host adapter closures, and thunks are all short-lived and nothing new is retained across poll cycles. Fix these issues in Kilo Cloud Previous review (commit 81b9c21)Status: 1 Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (7 files)
No memory leaks found: the seed map, host adapter closures, and thunks are all short-lived and nothing new is retained across poll cycles. Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0 Review guidance: REVIEW.md from base branch |
…l sync A branch callback that rejects during batched seeding propagated out of `seed()` and skipped the whole full sync, where the previous per-worktree lookup only failed that one worktree. Resolve each worktree's branch independently and guard the seed call so those worktrees fall back to the legacy path.
…s stale Batched seeding kept spawning branch lookups for every remaining worktree after a stop or restart superseded the poll generation, and then discarded the result. Break out of the loop as soon as the generation is stale.
What Problem This Solves
A full Agent Manager sync resolved each worktree with 2 to 3
ghcalls:gh pr viewplus areviewThreadsGraphQL querygh pr view,gh pr view <branch>,gh pr list --search <sha>With N worktrees that scales linearly in GraphQL points and process spawns against a 5,000 point/hour budget shared with every agent session that also runs
gh. GraphQL requests arePOST, so ETag and304revalidation do not apply, which is why caching alone cannot reduce this cost.Why This Change Was Made
Batching is the available lever while staying on
gh. One aliased GraphQL document resolves every worktree byheadRefName, with a HEAD-SHA alias for same-repo PRs whose local branch was renamed. The response is reshaped into thegh pr view --jsonshape soparsePRResult,checks, andparseReviewersare reused unchanged, and so full-sync and active-tick results hash identically, which avoids badge flicker.Selection mirrors gh's own finder because the naive query produces wrong badges:
[OPEN, CLOSED, MERGED], newest first, open preferred, so a merged PR keeps its badge instead of disappearingheadRefName: "main"on this repository alone matches 76 fork PRsmainsits on the last merged PR's squash commitAnything the batch cannot decide stays unresolved so the existing per-worktree path runs: ambiguous candidates,
refs/pull/N/headtracking refs, GraphQL errors, and unknown fields. A failed batch is never worse than the previous behavior.The seed orchestration lives in
pr/am-pr-seed.tsbehind a small callback host.PRStatusPoller.tsgrows only by the call site, a host adapter, and one optionalfetchOneparameter.User Impact
PR badges stay correct with fewer GitHub API calls and fewer
ghprocess spawns. The selected worktree keeps its 15s refresh, the all-worktree sweep stays at 2 minutes, and multi-project is unaffected because each project already has its own poller batching only its own repository.Evidence
Measured against
Kilo-Org/kilocodewithGH_DEBUG=apiandrateLimit { cost }:gh pr viewcosts 1 GraphQL point; onereviewThreadsquery costs 1 pointcost: 2to3, 0 errors, all 10 resolved including 6 fork PRs and an ambiguous fork pairIsolated VS Code on a disposable fixture with two open PRs, one merged PR whose remote branch was deleted, and one fresh branch off
main, counting only children of the extension host during one full sync:gh api graphql, 3reviewThreadsfollow-ups, 0gh pr vieworgh pr listBatched PR lookup cost: 1logged each 2 minutesghunauthenticated: one batch failure logged, then the legacy path reported the same auth state as beforeChecks:
tests/unit/5,620 pass, 0 fail;typecheck,lint,knip,check-kilocode-change, andformatclean.Limitations: the per-PR
reviewThreadsquery and the 15s active tick are unchanged, so at 10 worktrees the hourly reduction is roughly a quarter and grows with worktree count.