Merged
Size
M
Change Breakdown
Bug Fix85%
Refactor15%
#27895fix(core): Fix missing isolate acquisition and VM globals in expression engine

Expression engine VM mode bugs fixed for webhooks

Workflows with expression variables now work correctly in VM sandbox mode — webhook activation, conflict detection, and expression evaluation no longer fail silently or throw errors.

Expression variables like $execution, $vars, $secrets, and the node selector function $() are now properly available in VM-isolated expression evaluation. Several code paths that handle webhook registration, conflict checking, and workflow activation were bypassing the isolate pool, causing expressions to fail or return undefined in sandboxed environments. These paths now correctly acquire and release isolates, ensuring consistent expression resolution across all execution modes.

View Original GitHub DescriptionFact Check

Summary

Fix two classes of bugs surfaced by e2e testing with N8N_EXPRESSION_ENGINE=vm.

e2e run: https://github.com/n8n-io/n8n/actions/runs/23856758598

Missing isolate acquisition in CLI code paths

Some code paths that evaluate expressions were calling into the expression engine without first acquiring an isolate from the pool. This caused failures (or silent wrong results) in VM mode for:

  • Webhook registration during workflow activation, in active-workflow-manager.ts
  • Webhook conflict checks on save, in workflow.service.ts
  • Test webhook registration, in test-webhooks.ts
  • Webhook deactivation, in active-workflow-manager.ts

Each path now wraps the relevant call in acquireIsolate() / releaseIsolate().

Missing globals in VM isolate

The isolate-side resetDataProxies() was not exposing several variables that tournament-transformed expressions expect to find on __data. When missing, tournament's "$foo" in this ? this.$foo : global.$foo check fell through to global.$foo (also undefined), silently returning undefined or throwing.

Added to __data and globalThis:

  • $execution, $vars, $secrets — as lazy proxies
  • $() — node-selector function backed by a deep lazy proxy
  • $executionId, $resumeWebhookUrl, $webhookId, $nodeId, $nodeVersion — fetched as primitives via the host callback

Related Linear tickets, Github issues, and Community forum posts

https://linear.app/n8n/issue/CAT-2529

Review / Merge checklist

  • PR title and summary are descriptive. (conventions)
  • Docs updated or follow-up ticket created.
  • Tests included.
  • PR Labeled with Backport to Beta, Backport to Stable, or Backport to v1 (if the PR is an urgent fix that needs to be backported)
© 2026 · via Gitpulse