Skip to content

design(core): a fetchQuery() in flight during a sign-in or sign-out rejects with TanStack's CancelledError, which is neither a CallError nor a CanisterError #647

Description

@b3hr4d

DESIGN (not implemented: the fix changes behaviour the maintainer should choose).

What happens

ClientManager.updateAgent cancels every in-flight query of the registered canisters (queryClient.cancelQueries({ queryKey: [canisterId] })), so a result fetched under the old identity is not cached under the new one. For a query with an observer that is harmless: TanStack reverts it and the observer refetches after the invalidation. An imperative reactor.fetchQuery() (and so the React factories' .fetch(), which route loaders use) has no observer. Its promise rejects with TanStack's CancelledError { revert: true }.

Measured

A fetchQuery({ functionName: "balance" }) held open, then updateAgent(newIdentity), then the query answered:

{ name: "CancelledError", isCancelled: true, isCallError: false, isCanisterError: false }

The documented error type of a reactor call is CanisterError | CallError (ReactorReturnErr). A loader running while the user signs out therefore fails with an error type the app was never told about, and the router shows its error boundary for a sign-out.

Options

  1. Refetch for the new identity. In Reactor.fetchQuery, when the fetch rejects with isCancelledError and clientManager.identity differs from the one the fetch started under, fetch again and resolve with that. The caller gets data for the identity the app now has, as a mounted observer would. The old result is still never cached.
  2. Rethrow as a documented type: a CallError whose cause is the CancelledError and whose message says the identity changed. Callers can recognise it, but every loader still has to handle it.
  3. Document it: fetchQuery can reject with CancelledError across an identity change; check it with TanStack's isCancelledError.

Recommendation

Option 1. The cancellation exists to keep the old identity's answer out of the cache, not to fail the caller, and option 1 keeps that guarantee.

Acceptance

  • A fetchQuery in flight across updateAgent resolves with data fetched under the new identity (or rejects with a documented type, per the option chosen).
  • The old identity's result is still never cached.

Activity

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