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
- 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.
- 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.
- 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
DESIGN (not implemented: the fix changes behaviour the maintainer should choose).
What happens
ClientManager.updateAgentcancels 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 imperativereactor.fetchQuery()(and so the React factories'.fetch(), which route loaders use) has no observer. Its promise rejects with TanStack'sCancelledError { revert: true }.Measured
A
fetchQuery({ functionName: "balance" })held open, thenupdateAgent(newIdentity), then the query answered: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
Reactor.fetchQuery, when the fetch rejects withisCancelledErrorandclientManager.identitydiffers 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.CallErrorwhose cause is theCancelledErrorand whose message says the identity changed. Callers can recognise it, but every loader still has to handle it.fetchQuerycan reject withCancelledErroracross an identity change; check it with TanStack'sisCancelledError.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
fetchQueryin flight acrossupdateAgentresolves with data fetched under the new identity (or rejects with a documented type, per the option chosen).