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.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21822?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Félix Malfait
2026-06-19 10:24:31 +02:00
committed by GitHub
parent 569d887d1e
commit a6aa03b77b
@@ -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)) {