Skip to content

[Bug]: On github.com, PR sync rewrites every open linked PR twice after each base-branch push, because GitHub briefly reports mergeable UNKNOWN #23860

Closed as duplicate

Description

@WhiteWarrior625

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

  1. Link open github.com pull requests to many threads (we have 683 threads linking 61 open PRs in one repository, up to 268 threads per PR). Leave some of the PRs untouched for days, with merge conflicts or not.
  2. Merge anything into their base branch (main).
  3. Count thread.pull-request-link-updated rows in orchestration_events for the next 10 minutes, and diff each thread's consecutive payloads for the same PR.

Expected behavior

A push to the base branch writes nothing for PRs whose snapshot did not change. GitHub's mergeable: UNKNOWN means "still being calculated", so it should not replace a known value.

Actual behavior

After every push to the base branch, every linked thread gets two new link events for every open PR on that base, with nothing else about the PR changed:

  1. GitHub marks open PRs' mergeability stale on a base push and answers mergeable: UNKNOWN until it recomputes. toMergeability maps that to "unknown" (gitHubPullRequestJson.ts#L518-L527).
  2. snapshotFieldsEqual compares mergeability strictly (PullRequestSyncReactor.ts#L104), so conflicting → unknown counts as a change. syncEntry dispatches thread.pull-request-link.sync (#L267-L315).
  3. On the next quiet read, about 5 min later (#L217), GitHub has finished, unknown → conflicting (or mergeable) counts as a change again, and everything is rewritten back.
  4. The dispatch happens once per linked thread (#L413-L426), so one PR linked from 268 threads costs 268 events each way.

This is the github.com counterpart of #20273. #20352 fixed the ghe.com cause (tokenized avatar URLs). github.com avatar URLs are stable (?v=4), so that fix doesn't apply here. The cause on github.com is the transient UNKNOWN. The result is the same: #19248 ("persist only changed links") is defeated for every open PR, once per base push.

Measured on our server (0.1.2-nightly.20261022.3541), read-only, from the newest 5,000 rows of orchestration_events plus indexed per-thread lookups:

  • Newest 5,000 events (21:11:40–21:24:05Z): 946 are thread.pull-request-link-updated, across 683 threads and 61 distinct PRs. All 946 are state: open. One sweep at 21:15Z wrote 784 of them within a minute. One PR, untouched since 2026-10-05, got 268 events (one per linking thread) in that sweep.
  • Base-branch merges landed at 21:09:26Z and 21:09:33Z. The sweep at 21:09:41Z stored unknown, and the one at 21:14–21:15Z stored the real value back.
  • In a random sample of 45 threads, comparing each thread's last two events for the same PR: 43 differ in mergeability (+ syncedAt), all of them unknown → conflicting|mergeable. The other 2 had a real updatedAt change. None differed in syncedAt alone.
  • One thread's history for a PR last updated 2026-10-05: 12 events from 17:22Z to 21:15Z, alternating unknown/conflicting, with updatedAt unchanged. Each unknown follows a merge into main by seconds (merges at 17:58:33–53Z → unknown synced 17:58:58Z; merge at 18:39:17Z → 18:39:55Z; merges at 21:09:26–33Z → 21:09:41Z).
  • An earlier 25-minute window (18:37–19:02Z, which had several base merges): 1,131 link-updated events across 679 threads.

Consecutive payloads for one thread (repository, title, branches and author redacted):

 {"host":"github.com","repository":"<owner>/<repo>","number":15,"source":"agent","linkedAt":"2026-10-04T00:29:41.413Z",
  "snapshot":{"state":"open","isDraft":true,"updatedAt":"2026-10-05T21:32:46Z",
-   "syncedAt":"2026-10-22T19:13:41.848Z", ... "mergeability":"conflicting"},
+   "syncedAt":"2026-10-22T21:09:41.590Z", ... "mergeability":"unknown"},
 ...
-   "syncedAt":"2026-10-22T21:09:41.590Z", ... "mergeability":"unknown"},
+   "syncedAt":"2026-10-22T21:14:56.730Z", ... "mergeability":"conflicting"},

On a large database these bursts are hundreds of write transactions within seconds, in the same minute a base merge usually triggers other work.

Impact

Major degradation or frequent failure

Version or commit

0.1.2-nightly.20261022.3541 (044f072). Same code on main @ 2c8bdf6.

Environment

Linux desktop, github.com via gh, one busy private repository with frequent merges to main, 41 GB statev2.sqlite.

Logs or stack traces

# newest 5,000 rows of orchestration_events, thread.pull-request-link-updated only, per minute (UTC)
21:13  53 events / 19 PRs
21:14  30 / 1
21:15 784 / 35   <- the read after the 21:09 base merges, writing the real mergeability back
21:19  47 / 17

Suggested fix

  1. Treat an incoming mergeability: "unknown" as "no information" when the stored snapshot has a known value: keep the stored value, or leave mergeability out of the comparison for that read. Providers that always report unknown (Forgejo, Bitbucket, gitcafe) are unaffected, since unknown === unknown.
  2. Optional: needsStack uses the same comparison (#L340-L349), so a mergeability flap can also cost a stack read where knownUnstacked is false.

Workaround

Settle or unlink threads that link long-idle PRs. Each such link stops costing once every thread that links it is settled.

Related

Activity

  1. juliusmarminge commented on Nov 12, 2026

    @juliusmarminge
    Member

    This is the same problem as #22022, so I'm closing it in favour of that one. If it is different, say how and it will be reopened.

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