{"record":{"id":"223058647c38414f","repo":"gitbutlerapp/gitbutler","slug":"refetch-can-not-be-called-on-listeningsubscription","errorCode":null,"errorMessage":"Refetch can not be called on ListeningSubscription","messagePattern":"Refetch can not be called on ListeningSubscription","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/shared/src/lib/interest/interestStore.ts","lineNumber":121,"sourceCode":"\t\tif (this.counter === 0) {\n\t\t\tthis.unsubscribe = (await this.upsert()) || undefined;\n\t\t}\n\n\t\t++this.counter;\n\n\t\t// Unsubscribe function\n\t\treturn async () => {\n\t\t\t--this.counter;\n\n\t\t\tif (this.counter <= 0) {\n\t\t\t\tawait this.unsubscribe?.();\n\t\t\t}\n\t\t};\n\t}\n\n\tasync refetch(): Promise<void> {\n\t\t// Refetch does not make sense for listening subscriptions\n\t\tthrow new Error(\"Refetch can not be called on ListeningSubscription\");\n\t}\n}\n\n/**\n * The interest store is responsible for managing the subscriptions produced by\n * given service. There will only ever be one interest for each set of `args`\n * provided. This allows us to avoid making duplicate or supuflous requests.\n *\n * If it's constructed with a `frequency` the start notifier will get called first\n * when the subscription is first registered, and then many subsequent times,\n * until it is no longer registered.\n *\n * If it's constructed without a `frequency`, the start notifier will only get\n * called once before it's stop notifier is called.\n */\nexport class InterestStore<Arguments> {\n\tprivate readonly subscriptions: Subscription<Arguments>[] = [];\n","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/packages/shared/src/lib/interest/interestStore.ts#L103-L139","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nasync function refreshAll(subscriptions: Subscription<unknown>[]) {\n  await Promise.all(subscriptions.map((s) => s.refetch()));\n}\n// after\nasync function refreshAll(subscriptions: Subscription<unknown>[]) {\n  await Promise.all(\n    subscriptions.map((s) =>\n      s instanceof PollingSubscription ? s.refetch() : Promise.resolve(),\n    ),\n  );\n}","handlingStrategy":"try-catch","validationCode":"const isPolling = (store as { frequency?: number }).frequency !== undefined;\nif (!isPolling) return; // skip refetch for listening subscriptions","typeGuard":"function isPollingSubscription(s: Subscription<unknown>): s is PollingSubscription<unknown> {\n  return s instanceof PollingSubscription;\n}","tryCatchPattern":"try {\n  await subscription.refetch();\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"ListeningSubscription\")) {\n    // Listening data updates itself; nothing to refetch\n    return;\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["unsupported-operation","subscription","reactive-state"],"backgroundTag":"unsupported-operation","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}