Skip to content

[Bug]: Threads whose branch PR is merged or closed re-read git state every minute #24098

Open

Description

@Batuhan1999

Area

apps/server

Steps to reproduce

  1. Have a thread on a feature branch whose pull request is merged or closed, and keep the thread unsettled. Any of these does it:
    • the user wrote in the thread after the merge, so the merge does not settle it
    • sidebarAutoSettleOnMerge is off
    • sidebarAutoSettleKeepPullRequests is on, or inactivity settling is off
  2. Leave the server running. The settlement sweep and the PR-sync sweep each run once a minute.
  3. Count git processes, for example with runGitCommand spans in server.trace.ndjson or a process monitor.

Expected behavior

Once a branch's PR answer and git state are cached, a sweep that only finds "merged, but this thread stays" starts no git process. That is what happens for an open PR, and #17063's 5-minute git-state cache (PR_LOOKUP_GIT_STATE_TTL) is meant to allow it. Fresh git reads should happen only where an answer is about to settle a thread or be saved as a link.

Actual behavior

GitManager.branchPullRequest skips the git-state cache for every merged or closed answer, unless the caller already asked for fresh state (apps/server/src/git/GitManager.ts:2693 on main):

if (pullRequest === null || pullRequest.state === "open" || freshGitState) return pullRequest;
return yield* lookupBranchPullRequest(cacheCwd, branch, { refresh: false, freshGitState: true });

The fresh lookup runs git remote, git for-each-ref, the remote default-branch read and one or two git config reads. That is 4 to 6 processes; a real-git test with an origin-tracking branch counts 4.

Two once-a-minute sweeps call it without refresh, so each pays that confirm every minute:

  • ThreadSettlementService.ts:503 (sweep scheduled at :622) passes the branch lookup's answer to settleThread. When the answer is terminal and the thread stays, nothing happens, and the confirm was wasted.
  • ThreadPullRequestService.ts:197 (loop at :400) uses the answer to plan link updates. When the thread already holds that PR, nothing happens. When it would write, it already rereads git state with { freshGitState: true } at :275 before dispatching.

So each unsettled checkout and branch with a terminal PR costs about 8 to 12 git processes a minute, for as long as the thread exists. Open PRs and branches without a PR cost nothing on a warm cache.

#17063 added this confirm on purpose: a merged or closed answer can settle a thread or remove a worktree, so that decision must not rest on remembered git state. Neither sweep needs the confirm before it decides whether to act:

  • Settlement's linked-PR path already checks wouldSettle first and confirms with refresh: true only when it would settle (ThreadSettlementService.ts:482-485). The branch path can do the same.
  • PR sync confirms with freshGitState: true (:275) before every write.

Impact

Minor bug or occasional failure

There are no wrong results, only steady background load. Each git process costs more than a git run alone: on Windows #17063 measured about 90 ms of CPU each, and Node starts every one synchronously on the main thread. One user's telemetry from the Oct 11 nightly showed PR-lookup git work as the largest server load, about 100 git processes a minute while active. #17063's cache removed most of that. The terminal-state path described here is what remains. Its cost on current main is worked out from reading the code and from the test's spawn count. I have not measured it on a running server.

Version or commit

main @ 16aaf5b

Environment

Any OS. Server-side background sweeps. The cost per process is highest on Windows.

Workaround

Settle or archive threads whose PR is merged or closed. Settled and archived threads leave both sweeps.

Activity

  1. added a commit that references this issue on Oct 24, 2026
    849e62d
  2. brandus1 commented on Oct 31, 2026

    @brandus1

    Seen at scale on 0.1.6-nightly.20261030.3917, Linux arm64 headless server: about 390 worktrees and 1,466 settled threads with a worktree and branch, which together link 4,146 PRs, nearly all merged or closed.

    t3 trace summary over 28 minutes:

    • runGitCommand: 832/min, p50 477 ms (about 6–7 git processes alive at any moment);
    • GitVcsDriver.readConfigValue: 349/min;
    • GitManager.rememberPrLookupGitState: 319/min;
    • lookupBranchPullRequest / branchPullRequest: 64/min, p50 6.5–7.2 s.

    The service log over one day: 24,509 "pull request sync skipped" and 3,033 "thread branch pull request lookup failed".

    As a workaround we are unlinking merged and closed PRs from settled threads, and removing worktrees whose branch has merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions