fix(core): Acquire expression isolate when reconciling unregistered webhooks during publication (no-changelog) (#33341)

Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
This commit is contained in:
Tomi Turtiainen
2026-07-01 08:29:12 +00:00
committed by GitHub
co-authored by linear-code[bot]
parent 5cf8645a7c
commit a5ac4259b8
2 changed files with 60 additions and 5 deletions
@@ -167,6 +167,54 @@ describe('WorkflowTriggerActivator', () => {
);
});
test('brackets the registrar call with an acquired isolate so path expressions resolve', async () => {
const callOrder: string[] = [];
vi.spyOn(WorkflowExpression.prototype, 'acquireIsolate').mockImplementation(async () => {
callOrder.push('acquire');
});
vi.spyOn(WorkflowExpression.prototype, 'releaseIsolate').mockImplementation(async () => {
callOrder.push('release');
});
vi.spyOn(WorkflowExecuteAdditionalData, 'getBase').mockResolvedValue(
mock<IWorkflowExecuteAdditionalData>(),
);
const webhookTriggerRegistrar = mock<WebhookTriggerRegistrar>();
webhookTriggerRegistrar.getNodesWithUnregisteredWebhooks.mockImplementation(async () => {
callOrder.push('resolve');
return new Set(['w']);
});
const activator = buildActivator({ webhookTriggerRegistrar });
await activator.getNodesWithUnregisteredWebhooks(
mock<WorkflowEntity>({ id: 'wf-1', name: 'Test workflow', staticData: {}, settings: {} }),
{ nodes: [node('w', 'webhook')], connections: {} },
);
expect(callOrder).toEqual(['acquire', 'resolve', 'release']);
});
test('releases the isolate when the registrar throws', async () => {
vi.spyOn(WorkflowExecuteAdditionalData, 'getBase').mockResolvedValue(
mock<IWorkflowExecuteAdditionalData>(),
);
const releaseIsolate = vi
.spyOn(WorkflowExpression.prototype, 'releaseIsolate')
.mockResolvedValue(undefined);
vi.spyOn(WorkflowExpression.prototype, 'acquireIsolate').mockResolvedValue(undefined);
const webhookTriggerRegistrar = mock<WebhookTriggerRegistrar>();
webhookTriggerRegistrar.getNodesWithUnregisteredWebhooks.mockRejectedValue(new Error('boom'));
const activator = buildActivator({ webhookTriggerRegistrar });
await expect(
activator.getNodesWithUnregisteredWebhooks(
mock<WorkflowEntity>({ id: 'wf-1', name: 'Test workflow', staticData: {}, settings: {} }),
{ nodes: [node('w', 'webhook')], connections: {} },
),
).rejects.toThrow('boom');
expect(releaseIsolate).toHaveBeenCalledTimes(1);
});
test('returns empty without calling the registrar when there are no trigger nodes', async () => {
const webhookTriggerRegistrar = mock<WebhookTriggerRegistrar>();
const activator = buildActivator({ webhookTriggerRegistrar });
@@ -155,11 +155,18 @@ export class WorkflowTriggerActivator {
workflowSettings: dbWorkflow.settings,
});
return await this.webhookTriggerRegistrar.getNodesWithUnregisteredWebhooks(
workflow,
additionalData,
desiredNodes,
);
// Resolving webhook triggers evaluates each node's `path`/`httpMethod`
// expressions (e.g. the Form Trigger's dynamic path), which needs an isolate.
await workflow.expression.acquireIsolate();
try {
return await this.webhookTriggerRegistrar.getNodesWithUnregisteredWebhooks(
workflow,
additionalData,
desiredNodes,
);
} finally {
await workflow.expression.releaseIsolate();
}
}
/**