Skip to content

fix: stop notification polling from multiplying API requests#3066

Merged
afonsojramos merged 4 commits into
mainfrom
fix/notification-polling-storm
Jul 23, 2026
Merged

fix: stop notification polling from multiplying API requests#3066
afonsojramos merged 4 commits into
mainfrom
fix/notification-polling-storm

Conversation

@afonsojramos

Copy link
Copy Markdown
Member

Summary

Upgrading to v7 could multiply notification API traffic by an order of magnitude against GitHub/GHES (15k+ extra connections reported). The TanStack Query refactor made every useNotifications consumer (each notification row, repo group, sidebar, header, filter panel) a query observer, and TanStack Query schedules refetchInterval per mounted observer, not per query. Each observer polled on its own staggered timer, so their fetches never deduped, and refetchIntervalInBackground: true kept all of them firing while the menubar sat hidden. A full notification list produced dozens of complete fetch + GraphQL-enrichment cycles per interval instead of one.

Three fixes, in commit order:

  • Singleton polling ownershiprefetchInterval, refetchOnMount, refetchOnReconnect and refetchOnWindowFocus are now gated behind the existing withSideEffects flag, so only the app-level GlobalEffects host schedules refetches. All other consumers share the cached data, exactly as they already did for sound/native alerts.
  • IPC listener leaks — the same per-consumer pattern registered a permanent SYSTEM_WAKE listener per mounted row (the preload bridge offered no unsubscribe at all), piling up stale closures that each fired a refetch on every wake; the OAuth web flow likewise leaked one AUTH_CALLBACK listener per login attempt. Preload event registrations now return unsubscribe functions, effects clean up on unmount, the wake listener is host-only, and the auth listener is removed once the flow settles.
  • Retry cooldown — a failed poll retried near-instantly (v6 never retried within a poll), doubling request volume against an already-struggling server. The single retry now waits 30 seconds.

Validation

  • New regression test mounts a side-effects host plus staggered plain consumers and asserts one fetch per interval; against the old code it fails with 7 fetches where 4 are expected, reproducing the multiplication.
  • New tests assert plain consumers register zero wake listeners and that preload unsubscribe actually removes the ipcRenderer listener.
  • All touched-file suites pass (52 tests); vp check (format + lint + tsc) clean.

Closes #3065

…ests

TanStack Query schedules refetchInterval per mounted observer, not per
query, so every useNotifications consumer (each notification row, repo
group, sidebar, filter panel, etc.) ran its own staggered poll timer.
A full notification list produced dozens of full fetch + enrichment
cycles per interval instead of one, hammering the GitHub/GHES API.

Gate refetchInterval, refetchOnMount, refetchOnReconnect and
refetchOnWindowFocus behind withSideEffects so only the singleton
GlobalEffects host owns polling; all other consumers share the cache.
onRendererEvent registered ipcRenderer listeners with no way to remove
them, so every listener was permanent. Return an unsubscribe function
from onRendererEvent, onResetApp, onSystemWake and onAuthCallback so
callers can clean up.
Every useNotifications consumer (each notification row, repo group,
sidebar, etc.) registered a permanent SYSTEM_WAKE ipcRenderer listener,
piling up stale closures that all fired refetches on every wake. The
OAuth web flow likewise accumulated one permanent AUTH_CALLBACK listener
per login attempt.

Gate the wake listener behind withSideEffects so only the singleton
GlobalEffects host registers it, unsubscribe on effect cleanup
everywhere, and drop the auth callback listener once the flow settles.
A failed poll retried almost immediately, doubling request volume
against an already-struggling GitHub/GHES instance. Keep the single
retry but wait 30 seconds first so outages are not amplified.
@afonsojramos
afonsojramos requested a review from setchy as a code owner July 23, 2026 14:32
@github-actions github-actions Bot added the bug Something isn't working label Jul 23, 2026
@sonarqubecloud

Copy link
Copy Markdown

@afonsojramos
afonsojramos merged commit a40bd76 into main Jul 23, 2026
17 checks passed
@afonsojramos
afonsojramos deleted the fix/notification-polling-storm branch July 23, 2026 14:45
@github-actions github-actions Bot added this to the Release 7.x.x milestone Jul 23, 2026
refetchOnWindowFocus: true,
// Only the singleton side-effects host polls. Other consumers share the
// cached data and would otherwise each schedule their own refetch timer.
refetchInterval: withSideEffects && fetchType === FetchType.INTERVAL ? fetchIntervalMs : false,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A comment for future us... I was thinking we can drop the FetchType setting now that we're using Tanstack Query since it's better at managing the fetch interval, background fetch, fetch on reconnect, etc

@setchy

setchy commented Jul 23, 2026

Copy link
Copy Markdown
Member

Interesting - I wonder why/how I didn't have this within Atlassify. Must be handling this in a different way.

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

Labels

bug Something isn't working

Development

Successfully merging this pull request may close these issues.

Rate limit hit with v7

2 participants