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
- Do not call refetch() on interests from a frequency-less InterestStore; gate the call on the subscription type
- 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
- Handle the error in the caller (catch and no-op or log) so a generic refresh path does not crash
- 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
- Track which InterestStore instances are created without a frequency and never attach refresh UI to their interests
- Centralize refetch behind a helper that no-ops for listening subscriptions
- Prefer re-subscription (teardown + register) as the universal refresh mechanism
- Add a test asserting the shared refresh path skips frequency-less stores
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
- Bare repositories are not supported.
- Bitbucket does not support reopening a declined pull…
- `but setup` cannot run from a linked worktree; run it from…
- Cannot currently rebase a commit so that it becomes the…
- Cannot reorder ' ' in single-branch mode without branch…
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)