Repository navigation
fix(jetbrains): refresh a worktree's PR when its agent stops - #14902
Conversation
A pull request opened from inside a worktree — `gh pr create` in an agent session, the usual way one appears in Agent Manager — changes neither the branch nor the head commit. Those are exactly what the backend keys its "this checkout has no pull request" answer by, so the badge stayed hidden until that answer expired: up to PR_ABSENT_TTL, 10 minutes, plus a poll. Nothing local moved, so neither the 120s poll nor a return to the IDE could tell the difference, and only an unrelated worktree mutation (which clears the resolver wholesale) dislodged it sooner. Watch session activity for worktrees whose agent just stopped and resolve those rows from `gh` on the next lookup. The request goes through the existing hold/spend path, so a floor or an in-flight lookup defers it instead of dropping it, and a burst of turn endings collapses onto one lookup carrying every path it named. `prStatus` grows a `fresh` path list for this rather than reusing `maxAge = 0`: a repository with a dozen worktrees would otherwise re-run every PR-less checkout's full `gh` ladder to notice a pull request on one of them.
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Reviewed the new Files Reviewed (8 files)
Reviewed by deepseek-v4.1-flash · Input: 181.7K · Output: 32.7K · Cached: 1.9M Review guidance: REVIEW.md from base branch |
Problem
A worktree's PR badge in Agent Manager can take up to ~10 minutes to appear after the agent working in that worktree opens the pull request.
From a local log, PR #14897 on branch
fluffy-islandwas created at09:39:45. The badge appeared at09:44:23— and only because an unrelated worktree was created at09:43:52, which clears the resolver's caches wholesale:ghwas healthy throughout (every probevalue=OK exit=0, 296–1946 ms, noUNAUTH/RATE_LIMITED/TIMEOUT), so this is cache invalidation, not a GitHub or auth problem.PrResolverrecords "this checkout has no pull request" keyed by path + branch + head commit, forPR_ABSENT_TTL(10 min).gh pr createchanges neither the branch nor the head, so the entry stays valid andghis never re-run for that row.resolver.clear()only runs from IDE-initiated git mutations (worktree add/remove/PR import), and nothing connects an agent opening a PR to it. The 120 s poll and the focus path both ask with a ceiling loose enough to be served that entry, so neither notices either.What changed
WorktreeStatusServicewatches live session activity and, for each worktree whose agent leavesRUNNING(turn ended, or stopped on a question, permission or error), records a PR lookup that must resolve that row fromgh. It goes through the existing hold/spend path, so the 30 s floor or an in-flight lookup defers it rather than dropping it, and a burst of turn endings collapses onto one lookup carrying every path it named.prStatusgrows afreshpath list for this instead of reusingmaxAge = 0. Naming paths keeps the cost proportional: the named rows re-run their lookup, every other row still answers from cache. ForcingmaxAge = 0globally would re-run every PR-less checkout's full three-callghladder to notice a pull request on one of them — the same fan-out that took 27 s across 13 worktrees in the log above.Impact
A PR opened by an agent shows its badge within one floor window of that agent stopping, instead of whenever the absence entry happens to expire. No new polling and no extra
ghcalls for worktrees nothing happened in.Not addressed
Two narrower gaps are left alone deliberately, both backstopped by the existing 10-minute TTL:
Away.ceilingaccepts a cached absence written during an absence, which can predate a change made in that same window. Tightening it would make every return from a >10 s absence pay a full fan-out; with this fix the agent-driven case no longer depends on it.Testing
./gradlew :frontend:test --tests "*WorktreeStatusServiceTest*"— 6 new tests (43 total, green). Verified they fail when the new transition handling is disabled../gradlew :backend:test --tests "*KiloWorktreeRpcApiImplTest*" --tests "*PrResolverTest*"— green, including a new case for the per-path ceiling../gradlew typecheckand the full./gradlew :frontend:testsuite — green../gradlew :backend:testas a whole has pre-existing flakiness inKiloBackendAppServiceTest/KiloBackendWorkspaceTest: a different subset fails on each run and the same classes fail onmainwith these changes stashed. Neither touches worktree or PR code.