From 8645ab6799052910d8819c25478eff42fbd3194b Mon Sep 17 00:00:00 2001 From: Usama Sadiq Date: Thu, 9 Jul 2026 17:00:20 +0500 Subject: [PATCH 1/3] Fix Redis crash loop from unhandled socket errors node-redis session and cache clients crash the process when Memorystore drops idle TLS connections. Add error handlers, pingInterval, TCP keepalive, and reconnect strategy to both the session-storage and cache-storage Redis clients. --- .../cache-storage.module-factory.ts | 29 +++++++++++++++---- .../session-storage.module-factory.ts | 17 +++++++++-- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/packages/twenty-server/src/engine/core-modules/cache-storage/cache-storage.module-factory.ts b/packages/twenty-server/src/engine/core-modules/cache-storage/cache-storage.module-factory.ts index 303cf9cd2182f..fb51cc9e4724c 100644 --- a/packages/twenty-server/src/engine/core-modules/cache-storage/cache-storage.module-factory.ts +++ b/packages/twenty-server/src/engine/core-modules/cache-storage/cache-storage.module-factory.ts @@ -1,13 +1,14 @@ import { type CacheModuleOptions } from '@nestjs/cache-manager'; -import { redisStore } from 'cache-manager-redis-yet'; +import { redisInsStore } from 'cache-manager-redis-yet'; +import { createClient } from 'redis'; import { CacheStorageType } from 'src/engine/core-modules/cache-storage/types/cache-storage-type.enum'; import { type TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; -export const cacheStorageModuleFactory = ( +export const cacheStorageModuleFactory = async ( twentyConfigService: TwentyConfigService, -): CacheModuleOptions => { +): Promise => { const cacheStorageType = CacheStorageType.Redis; const cacheStorageTtl = twentyConfigService.get('CACHE_STORAGE_TTL'); const cacheModuleOptions: CacheModuleOptions = { @@ -28,10 +29,28 @@ export const cacheStorageModuleFactory = ( ); } + const redisClient = createClient({ + url: redisUrl, + pingInterval: 30_000, + socket: { + keepAlive: true, + keepAliveInitialDelay: 30_000, + reconnectStrategy: (retries: number) => + Math.min(retries * 200, 5_000), + }, + }); + + redisClient.on('error', (err) => + console.error('Cache Redis client error:', err), + ); + + await redisClient.connect(); + return { ...cacheModuleOptions, - store: redisStore, - url: redisUrl, + store: redisInsStore(redisClient, { + ttl: cacheStorageTtl * 1000, + }), }; } default: diff --git a/packages/twenty-server/src/engine/core-modules/session-storage/session-storage.module-factory.ts b/packages/twenty-server/src/engine/core-modules/session-storage/session-storage.module-factory.ts index c8aab18e95272..21e7231900848 100644 --- a/packages/twenty-server/src/engine/core-modules/session-storage/session-storage.module-factory.ts +++ b/packages/twenty-server/src/engine/core-modules/session-storage/session-storage.module-factory.ts @@ -57,11 +57,22 @@ export const getSessionStorageOptions = ( const redisClient = createClient({ url: connectionString, + pingInterval: 30_000, + socket: { + keepAlive: true, + keepAliveInitialDelay: 30_000, + reconnectStrategy: (retries: number) => + Math.min(retries * 200, 5_000), + }, }); - redisClient.connect().catch((err) => { - throw new Error(`Redis connection failed: ${err}`); - }); + redisClient.on('error', (err) => + console.error('Session Redis client error:', err), + ); + + redisClient.connect().catch((err) => + console.error('Session Redis initial connect failed:', err), + ); return { ...sessionStorage, From 98a49bc93ccf965f6e684365ec676df9c9f5016a Mon Sep 17 00:00:00 2001 From: Usama Sadiq Date: Thu, 9 Jul 2026 17:23:51 +0500 Subject: [PATCH 2/3] Fix Prettier formatting on session storage factory --- .../session-storage/session-storage.module-factory.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/twenty-server/src/engine/core-modules/session-storage/session-storage.module-factory.ts b/packages/twenty-server/src/engine/core-modules/session-storage/session-storage.module-factory.ts index 21e7231900848..36ccf10a317e8 100644 --- a/packages/twenty-server/src/engine/core-modules/session-storage/session-storage.module-factory.ts +++ b/packages/twenty-server/src/engine/core-modules/session-storage/session-storage.module-factory.ts @@ -70,9 +70,11 @@ export const getSessionStorageOptions = ( console.error('Session Redis client error:', err), ); - redisClient.connect().catch((err) => - console.error('Session Redis initial connect failed:', err), - ); + redisClient + .connect() + .catch((err) => + console.error('Session Redis initial connect failed:', err), + ); return { ...sessionStorage, From 43106d4447817bcb875a3fe26757f767b438f0ac Mon Sep 17 00:00:00 2001 From: Usama Sadiq Date: Mon, 13 Jul 2026 15:59:29 +0500 Subject: [PATCH 3/3] Fix keepAlive typing and redisInsStore cast for tsc - Use keepAlive: 30_000 (number) instead of keepAlive: true (boolean) since node-redis v4 types socket.keepAlive as number | false - Remove keepAliveInitialDelay which node-redis ignores - Cast redisClient as RedisClientType for redisInsStore compatibility --- .../cache-storage/cache-storage.module-factory.ts | 7 +++---- .../session-storage/session-storage.module-factory.ts | 3 +-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/twenty-server/src/engine/core-modules/cache-storage/cache-storage.module-factory.ts b/packages/twenty-server/src/engine/core-modules/cache-storage/cache-storage.module-factory.ts index fb51cc9e4724c..cd068cde28587 100644 --- a/packages/twenty-server/src/engine/core-modules/cache-storage/cache-storage.module-factory.ts +++ b/packages/twenty-server/src/engine/core-modules/cache-storage/cache-storage.module-factory.ts @@ -1,7 +1,7 @@ import { type CacheModuleOptions } from '@nestjs/cache-manager'; import { redisInsStore } from 'cache-manager-redis-yet'; -import { createClient } from 'redis'; +import { createClient, type RedisClientType } from 'redis'; import { CacheStorageType } from 'src/engine/core-modules/cache-storage/types/cache-storage-type.enum'; import { type TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; @@ -33,8 +33,7 @@ export const cacheStorageModuleFactory = async ( url: redisUrl, pingInterval: 30_000, socket: { - keepAlive: true, - keepAliveInitialDelay: 30_000, + keepAlive: 30_000, reconnectStrategy: (retries: number) => Math.min(retries * 200, 5_000), }, @@ -48,7 +47,7 @@ export const cacheStorageModuleFactory = async ( return { ...cacheModuleOptions, - store: redisInsStore(redisClient, { + store: redisInsStore(redisClient as RedisClientType, { ttl: cacheStorageTtl * 1000, }), }; diff --git a/packages/twenty-server/src/engine/core-modules/session-storage/session-storage.module-factory.ts b/packages/twenty-server/src/engine/core-modules/session-storage/session-storage.module-factory.ts index 36ccf10a317e8..00308f987c38a 100644 --- a/packages/twenty-server/src/engine/core-modules/session-storage/session-storage.module-factory.ts +++ b/packages/twenty-server/src/engine/core-modules/session-storage/session-storage.module-factory.ts @@ -59,8 +59,7 @@ export const getSessionStorageOptions = ( url: connectionString, pingInterval: 30_000, socket: { - keepAlive: true, - keepAliveInitialDelay: 30_000, + keepAlive: 30_000, reconnectStrategy: (retries: number) => Math.min(retries * 200, 5_000), },