fix(core): Reset destination node after partial execution of tools (#16376)

This commit is contained in:
Benjamin Schroth
2025-06-16 16:04:31 +02:00
committed by GitHub
parent 4e73c40739
commit c3653275f2
5 changed files with 55 additions and 1 deletions

View File

@@ -84,6 +84,12 @@ describe('Execution Lifecycle Hooks', () => {
const taskStartedData = mock<ITaskStartedData>();
const taskData = mock<ITaskData>();
const runExecutionData = mock<IRunExecutionData>();
const successfulRunWithRewiredDestination = mock<IRun>({
status: 'success',
finished: true,
waitTill: undefined,
});
const successfulRun = mock<IRun>({
status: 'success',
finished: true,
@@ -123,6 +129,15 @@ describe('Execution Lifecycle Hooks', () => {
error: expressionError,
},
};
successfulRunWithRewiredDestination.data = {
startData: {
destinationNode: 'PartialExecutionToolExecutor',
originalDestinationNode: nodeName,
},
resultData: {
runData: {},
},
};
});
const workflowEventTests = (expectedUserId?: string) => {
@@ -154,6 +169,25 @@ describe('Execution Lifecycle Hooks', () => {
expect(eventService.emit).not.toHaveBeenCalledWith('workflow-post-execute');
});
it('should reset destination node to original destination', async () => {
await lifecycleHooks.runHook('workflowExecuteAfter', [
successfulRunWithRewiredDestination,
{},
]);
expect(eventService.emit).toHaveBeenCalledWith('workflow-post-execute', {
executionId,
runData: successfulRunWithRewiredDestination,
workflow: workflowData,
userId: expectedUserId,
});
expect(successfulRunWithRewiredDestination.data.startData?.destinationNode).toBe(nodeName);
expect(
successfulRunWithRewiredDestination.data.startData?.originalDestinationNode,
).toBeUndefined();
});
});
};

View File

@@ -44,6 +44,15 @@ function hookFunctionsWorkflowEvents(hooks: ExecutionLifecycleHooks, userId?: st
if (runData.status === 'waiting') return;
const { executionId, workflowData: workflow } = this;
if (runData.data.startData) {
const originalDestination = runData.data.startData.originalDestinationNode;
if (originalDestination) {
runData.data.startData.destinationNode = originalDestination;
runData.data.startData.originalDestinationNode = undefined;
}
}
eventService.emit('workflow-post-execute', { executionId, runData, workflow, userId });
});
}

View File

@@ -1,4 +1,5 @@
import { Logger } from '@n8n/backend-common';
import { TOOL_EXECUTOR_NODE_NAME } from '@n8n/constants';
import { Service } from '@n8n/di';
import * as a from 'assert/strict';
import {
@@ -151,7 +152,14 @@ export class ManualExecutionService {
workflow = graph.toWorkflow({
...workflow,
});
data.destinationNode = graph.getDirectChildConnections(destinationNode).at(0)?.to?.name;
// Save original destination
if (data.executionData) {
data.executionData.startData = data.executionData.startData ?? {};
data.executionData.startData.originalDestinationNode = data.destinationNode;
}
// Set destination to Tool Executor
data.destinationNode = TOOL_EXECUTOR_NODE_NAME;
}
}

View File

@@ -358,6 +358,7 @@ export class WorkflowExecute {
destinationNodeName,
'a destinationNodeName is required for the new partial execution flow',
);
const originalDestination = destinationNodeName;
let destination = workflow.getNode(destinationNodeName);
assert.ok(
@@ -456,6 +457,7 @@ export class WorkflowExecute {
this.runExecutionData = {
startData: {
destinationNode: destinationNodeName,
originalDestinationNode: originalDestination,
runNodeFilter: Array.from(filteredNodes.values()).map((node) => node.name),
},
resultData: {

View File

@@ -2152,6 +2152,7 @@ export interface IRunExecutionData {
startData?: {
startNodes?: StartNodeData[];
destinationNode?: string;
originalDestinationNode?: string;
runNodeFilter?: string[];
};
resultData: {