From 0d3c7a47af27d5a67fa36b27f109cc35bfdd4752 Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Fri, 5 Jun 2026 16:30:19 +0200 Subject: [PATCH] fix: guard against undefined logicFunctionId when destroying workflow CODE steps (#21256) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Adds `isDefined` guard in `handleLogicFunctionSubEntities` to skip CODE steps with undefined `logicFunctionId` instead of crashing - Adds same guard in `runWorkflowVersionStepDeletionSideEffects` for consistency - Rejects CODE steps in `create_complete_workflow` AI tool at runtime to prevent creating workflows with missing logic functions in the first place Fixes `"Logic function with id undefined not found"` INTERNAL_SERVER_ERROR when destroying workflows whose CODE steps were created via `create_complete_workflow` without a proper logic function. ## Test plan - [x] Destroy a workflow that has a CODE step with undefined logicFunctionId → should succeed silently - [x] Try creating a workflow with a CODE step via `create_complete_workflow` tool → should return error message - [x] Normal workflow destroy with valid CODE steps still deletes the logic function --- .../workflow-common.workspace-service.ts | 13 +++++++++++-- ...rsion-step-operations.workspace-service.spec.ts | 4 ++-- ...ow-version-step-operations.workspace-service.ts | 4 ++++ .../tools/create-complete-workflow.tool.ts | 14 ++++++++++++++ 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts b/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts index 3b5ae61b524..c94da4e6a07 100644 --- a/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts @@ -1,6 +1,6 @@ import { Injectable, Logger } from '@nestjs/common'; -import { isDefined } from 'twenty-shared/utils'; +import { isDefined, isValidUuid } from 'twenty-shared/utils'; import { CommandMenuItemService } from 'src/engine/metadata-modules/command-menu-item/command-menu-item.service'; import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; @@ -378,8 +378,17 @@ export class WorkflowCommonWorkspaceService { for (const workflowVersion of workflowVersions) { for (const step of workflowVersion.steps ?? []) { if (step.type === WorkflowActionType.CODE) { + const logicFunctionId = step.settings.input.logicFunctionId; + + if (!isValidUuid(logicFunctionId)) { + this.logger.warn( + `Skipping destroy for CODE step with undefined logicFunctionId in workflow ${workflowId}`, + ); + continue; + } + await this.logicFunctionFromSourceService.deleteOneWithSource({ - id: step.settings.input.logicFunctionId, + id: logicFunctionId, workspaceId, }); } diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step-operations.workspace-service.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step-operations.workspace-service.spec.ts index 5aef09efb80..b1e61e08634 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step-operations.workspace-service.spec.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step-operations.workspace-service.spec.ts @@ -188,7 +188,7 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => { nextStepIds: [], settings: { input: { - logicFunctionId: 'function-id', + logicFunctionId: '550e8400-e29b-41d4-a716-446655440000', }, outputSchema: {}, errorHandlingOptions: { @@ -206,7 +206,7 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => { expect( logicFunctionFromSourceService.deleteOneWithSource, ).toHaveBeenCalledWith({ - id: 'function-id', + id: '550e8400-e29b-41d4-a716-446655440000', workspaceId: mockWorkspaceId, }); }); diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service.ts index 32c9d86f524..38b0c13f112 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service.ts @@ -91,6 +91,10 @@ export class WorkflowVersionStepOperationsWorkspaceService { }) { switch (step.type) { case WorkflowActionType.CODE: { + if (!isValidUuid(step.settings.input.logicFunctionId)) { + break; + } + await this.logicFunctionFromSourceService.deleteOneWithSource({ id: step.settings.input.logicFunctionId, workspaceId, diff --git a/packages/twenty-server/src/modules/workflow/workflow-tools/tools/create-complete-workflow.tool.ts b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/create-complete-workflow.tool.ts index 4fcd3d5ef08..f0e14c654dc 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-tools/tools/create-complete-workflow.tool.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/create-complete-workflow.tool.ts @@ -9,6 +9,10 @@ import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-perm import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { WorkflowVersionStatus } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; import { WorkflowStatus } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity'; +import { + WorkflowVersionStepException, + WorkflowVersionStepExceptionCode, +} from 'src/modules/workflow/common/exceptions/workflow-version-step.exception'; import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; import { type WorkflowToolContext, @@ -116,6 +120,16 @@ This is the most efficient way for AI to create workflows as it handles all the activate?: boolean; }) => { try { + const codeSteps = parameters.steps.filter( + (step) => step.type === ('CODE' as string), + ); + + if (codeSteps.length > 0) { + throw new WorkflowVersionStepException( + 'CODE steps cannot be created via create_complete_workflow because it does not create the underlying logic function. Use create_workflow_version_step instead.', + WorkflowVersionStepExceptionCode.INVALID_REQUEST, + ); + } const workflowId = await createWorkflow({ deps, context,