gitbutlerapp/gitbutler · error

Refetch can not be called on ListeningSubscription

Error message

Refetch can not be called on ListeningSubscription

What it means

ListeningSubscription models a push/subscription-style source (registered once per set of args) rather than a pollable resource, so its refetch() deliberately throws. Calling refetch() on a listening subscription has no meaningful semantics because data updates arrive via the subscription itself, not via manual re-invocation of the upsert notifier. The library throws immediately to surface the misuse instead of silently doing nothing.

Solutions

  1. Do not call refetch() on interests from a frequency-less InterestStore; gate the call on the subscription type
  2. If you need both behaviors, keep the store in polling mode (pass a frequency) or implement refetch for ListeningSubscription by re-running the upsert notifier
  3. Handle the error in the caller (catch and no-op or log) so a generic refresh path does not crash
  4. Restructure so refresh is expressed by re-subscribing (tear down the interest and re-register it), which works for both subscription kinds

Example fix

// before
async function refreshAll(subscriptions: Subscription<unknown>[]) {
  await Promise.all(subscriptions.map((s) => s.refetch()));
}
// after
async function refreshAll(subscriptions: Subscription<unknown>[]) {
  await Promise.all(
    subscriptions.map((s) =>
      s instanceof PollingSubscription ? s.refetch() : Promise.resolve(),
    ),
  );
}
Defensive patterns

Strategy: try-catch

Validate before calling

const isPolling = (store as { frequency?: number }).frequency !== undefined;
if (!isPolling) return; // skip refetch for listening subscriptions

Type guard

function isPollingSubscription(s: Subscription<unknown>): s is PollingSubscription<unknown> {
  return s instanceof PollingSubscription;
}

Try / catch

try {
  await subscription.refetch();
} catch (e) {
  if (e instanceof Error && e.message.includes("ListeningSubscription")) {
    // Listening data updates itself; nothing to refetch
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling refetch() on the interest/subscription returned by an InterestStore that was constructed without a `frequency` (i.e. a ListeningSubscription). This typically happens via shared code that generically calls `subscription.refetch()` (e.g. a refresh button or invalidation helper) without knowing whether the subscription polls or listens.

Common situations: A UI 'refresh' action invokes refetch for all interests; a shared invalidation utility (originating from polling usage) is reused on a listening interest; a refactor switched an InterestStore from a polling frequency to no frequency while callers still called refetch.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/223058647c38414f. Report an issue: GitHub.

Appendix: source

Thrown at packages/shared/src/lib/interest/interestStore.ts:121

		if (this.counter === 0) {
			this.unsubscribe = (await this.upsert()) || undefined;
		}

		++this.counter;

		// Unsubscribe function
		return async () => {
			--this.counter;

			if (this.counter <= 0) {
				await this.unsubscribe?.();
			}
		};
	}

	async refetch(): Promise<void> {
		// Refetch does not make sense for listening subscriptions
		throw new Error("Refetch can not be called on ListeningSubscription");
	}
}

/**
 * The interest store is responsible for managing the subscriptions produced by
 * given service. There will only ever be one interest for each set of `args`
 * provided. This allows us to avoid making duplicate or supuflous requests.
 *
 * If it's constructed with a `frequency` the start notifier will get called first
 * when the subscription is first registered, and then many subsequent times,
 * until it is no longer registered.
 *
 * If it's constructed without a `frequency`, the start notifier will only get
 * called once before it's stop notifier is called.
 */
export class InterestStore<Arguments> {
	private readonly subscriptions: Subscription<Arguments>[] = [];

View on GitHub (pinned to 58e5313667)