From a6aa03b77b9f384f23f7f0fef83f1d0a31319d51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Fri, 19 Jun 2026 10:24:31 +0200 Subject: [PATCH] fix(front): emit error instead of completing empty on failed token renewal (#21822) handleTokenRenewal returned rxjs EMPTY when there was no token pair or token renewal failed, so the operation's link chain completed without emitting a value. Apollo Client v4 treats that as an invariant violation ("The link chain completed without emitting a value"), which surfaces as an uncaught error during auth/token transitions (e.g. the workspace-token swap in onboarding). Re-emit the original error so the operation rejects cleanly instead. Review in cubic --- .../src/modules/apollo/services/apollo.factory.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/twenty-front/src/modules/apollo/services/apollo.factory.ts b/packages/twenty-front/src/modules/apollo/services/apollo.factory.ts index c32ca606f8..905b756d0a 100644 --- a/packages/twenty-front/src/modules/apollo/services/apollo.factory.ts +++ b/packages/twenty-front/src/modules/apollo/services/apollo.factory.ts @@ -7,7 +7,7 @@ import { import { setContext } from '@apollo/client/link/context'; import { ErrorLink } from '@apollo/client/link/error'; import { RetryLink } from '@apollo/client/link/retry'; -import { EMPTY, from, switchMap } from 'rxjs'; +import { from, switchMap, throwError } from 'rxjs'; import { RestLink } from 'apollo-link-rest'; import UploadHttpLink from 'apollo-upload-client/UploadHttpLink.mjs'; @@ -180,11 +180,12 @@ export class ApolloFactory implements ApolloManager { const handleTokenRenewal = ( operation: ApolloLink.Operation, forward: ApolloLink.ForwardFunction, + error: ErrorLike, ) => { if (!getTokenPair()) { onUnauthenticatedError?.(); - return EMPTY; + return throwError(() => error); } if (!renewalPromise) { @@ -205,7 +206,9 @@ export class ApolloFactory implements ApolloManager { } return from(renewalPromise).pipe( - switchMap((succeeded) => (succeeded ? forward(operation) : EMPTY)), + switchMap((succeeded) => + succeeded ? forward(operation) : throwError(() => error), + ), ); }; @@ -274,7 +277,7 @@ export class ApolloFactory implements ApolloManager { if (graphQLError.message === 'Unauthorized') { // oxlint-disable-next-line no-console console.log('Unauthorized, triggering token renewal'); - return handleTokenRenewal(operation, forward); + return handleTokenRenewal(operation, forward, error); } switch (graphQLError?.extensions?.code) { @@ -288,7 +291,7 @@ export class ApolloFactory implements ApolloManager { case 'UNAUTHENTICATED': { // oxlint-disable-next-line no-console console.log('UNAUTHENTICATED, triggering token renewal'); - return handleTokenRenewal(operation, forward); + return handleTokenRenewal(operation, forward, error); } case 'NOT_FOUND': case 'BAD_USER_INPUT': @@ -320,7 +323,7 @@ export class ApolloFactory implements ApolloManager { console.log( 'Authentication error, triggering token renewal from errorLink', ); - return handleTokenRenewal(operation, forward); + return handleTokenRenewal(operation, forward, error); } if (this.isPayloadTooLargeError(error)) {