fix: guard against undefined logicFunctionId when destroying workflow CODE steps (#21256)

## 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
This commit is contained in:
Thomas Trompette
2026-06-05 16:30:19 +02:00
committed by GitHub
parent 2dbdd72e65
commit 0d3c7a47af
4 changed files with 31 additions and 4 deletions

View File

@@ -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,
});
}

View File

@@ -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,
});
});

View File

@@ -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,

View File

@@ -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,