diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda.driver.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda.driver.ts index d676cfc33f..190123899c 100644 --- a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda.driver.ts +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda.driver.ts @@ -25,6 +25,7 @@ import { LambdaAwsClientService } from 'src/engine/core-modules/logic-function/l import { LambdaExecutorManagerService } from 'src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/services/lambda-executor-manager.service'; import { LambdaLayerManagerService } from 'src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/services/lambda-layer-manager.service'; import { LambdaToolFunctionsService } from 'src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/services/lambda-tool-functions.service'; +import { buildLogicFunctionTimeoutResult } from 'src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/build-logic-function-timeout-result.util'; import { parseLambdaLogResult } from 'src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/parse-lambda-log-result.util'; import { LogicFunctionExecutionStatus } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-execution-result.dto'; import { LogicFunctionExecutionMode } from 'src/engine/metadata-modules/logic-function/logic-function.entity'; @@ -217,6 +218,18 @@ export class LambdaDriver implements LogicFunctionDriver { } catch (error) { const phaseTiming = `phase=${currentPhase} buildMs=${buildExecutorMs} fetchCodeMs=${getBuiltCodeMs}`; + const isTimeoutError = + error instanceof Error && error.name === 'TimeoutError'; + + if (isTimeoutError && currentPhase === LambdaExecutionPhase.INVOKE) { + // User-level outcome (function ran too long), not a platform error: return, don't throw. + this.logger.warn( + `Logic function '${flatLogicFunction.id}' timed out during invoke [${phaseTiming}]`, + ); + + return buildLogicFunctionTimeoutResult(timeoutMs); + } + this.logger.error( `Lambda invocation failed for function ${flatLogicFunction.id} [${phaseTiming}]: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error.stack : undefined, @@ -229,7 +242,8 @@ export class LambdaDriver implements LogicFunctionDriver { ); } - if (error instanceof Error && error.name === 'TimeoutError') { + if (isTimeoutError) { + // Build/fetch-phase timeouts are platform-side — keep throwing so they reach Sentry. const executor = await this.executorManager .getLambdaExecutor(flatLogicFunction) .catch(() => undefined); diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/__tests__/build-logic-function-timeout-result.util.spec.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/__tests__/build-logic-function-timeout-result.util.spec.ts new file mode 100644 index 0000000000..17f765b080 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/__tests__/build-logic-function-timeout-result.util.spec.ts @@ -0,0 +1,25 @@ +import { buildLogicFunctionTimeoutResult } from 'src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/build-logic-function-timeout-result.util'; +import { LogicFunctionExecutionStatus } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-execution-result.dto'; + +describe('buildLogicFunctionTimeoutResult', () => { + it('returns an ERROR result for a timeout', () => { + const result = buildLogicFunctionTimeoutResult(30_000); + + expect(result.status).toBe(LogicFunctionExecutionStatus.ERROR); + expect(result.data).toBeNull(); + expect(result.error?.errorType).toBe('TimeoutError'); + expect(result.error?.errorMessage).toBe( + 'Function execution timed out after 30s', + ); + expect(result.duration).toBe(30_000); + }); + + it('rounds the timeout to whole seconds', () => { + expect(buildLogicFunctionTimeoutResult(900_000).error?.errorMessage).toBe( + 'Function execution timed out after 900s', + ); + expect(buildLogicFunctionTimeoutResult(1_500).error?.errorMessage).toBe( + 'Function execution timed out after 2s', + ); + }); +}); diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/build-logic-function-timeout-result.util.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/build-logic-function-timeout-result.util.ts new file mode 100644 index 0000000000..79b48699ea --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda/utils/build-logic-function-timeout-result.util.ts @@ -0,0 +1,16 @@ +import { type LogicFunctionExecuteResult } from 'src/engine/core-modules/logic-function/logic-function-drivers/interfaces/logic-function-driver.interface'; +import { LogicFunctionExecutionStatus } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-execution-result.dto'; + +export const buildLogicFunctionTimeoutResult = ( + timeoutMs: number, +): LogicFunctionExecuteResult => ({ + data: null, + logs: '', + duration: timeoutMs, + status: LogicFunctionExecutionStatus.ERROR, + error: { + errorType: 'TimeoutError', + errorMessage: `Function execution timed out after ${Math.round(timeoutMs / 1_000)}s`, + stackTrace: [], + }, +});