Skip to content

[BUG] Variable references not injected into execution context, causing ReferenceError #2798

@PlaneInABottle

Description

@PlaneInABottle

Bug Description

When function/condition blocks reference undefined block variables (e.g., <webhook1.conversation.id> when webhook block hasn't executed), the variable reference is substituted into the code but the actual variable is never injected into the execution context, causing "ReferenceError: variable is not defined" at runtime.

This issue was discovered as a consequence of fixing #2794 - while the nullish coalescing fix addressed the substitution syntax, it revealed this deeper IPC serialization bug.

Root Cause

The variable substitution process has a multi-layer issue:

  1. Code Substitution Layer: <webhook1.conversation.id> is correctly replaced with __tag_webhook1_conversation_id in the code
  2. Context Variable Storage: The variable IS added to contextVariables map with undefined value
  3. IPC Serialization Layer: When contextVariables is serialized via IPC to the worker thread, JSON.stringify() strips keys with undefined values, causing those variables to not be transmitted
  4. Execution Layer: The worker receives an empty contextVariables object and can't inject the missing variables into the VM

Result: Code references __tag_webhook1_conversation_id but it's never defined in the execution context.

Error Manifestation

ReferenceError: __tag_webhook1_conversation_id is not defined

This occurs in:

  • E2B JavaScript execution path
  • E2B Python execution path
  • Isolated-VM execution path

Reproduction Steps

  1. Create a workflow with Start trigger and Webhook trigger
  2. Add Extract Context function block with: const x = <webhook1.optional_field> ?? "default";
  3. Execute workflow using Start trigger (webhook doesn't execute)
  4. Function block fails with "ReferenceError: __tag_webhook1_optional_field is not defined"

Root Issue: IPC Serialization

The key problem occurs in apps/sim/lib/execution/isolated-vm.ts:

// BROKEN - undefined values are stripped during JSON serialization in IPC
worker!.send({ type: 'execute', executionId, request: req })

When req.contextVariables contains { __tag_webhook_id: undefined }, the IPC serialization strips it, resulting in an empty object on the worker side.

Solution Applied

File 1: apps/sim/lib/execution/isolated-vm.ts

Add explicit sanitization BEFORE IPC transmission:

+  // Convert undefined values to null in contextVariables before IPC serialization.
+  // JSON.stringify() strips keys with undefined values, which would cause ReferenceErrors
+  // when user code references variables that weren't properly injected into the VM context.
+  const sanitizedContextVariables: Record<string, unknown> = {}
+  for (const [key, value] of Object.entries(req.contextVariables)) {
+    sanitizedContextVariables[key] = value === undefined ? null : value
+  }
+  const sanitizedReq = { ...req, contextVariables: sanitizedContextVariables }

   return new Promise((resolve) => {
     // ...
     try {
-      worker!.send({ type: 'execute', executionId, request: req })
+      worker!.send({ type: 'execute', executionId, request: sanitizedReq })
     } catch {

File 2: apps/sim/app/api/function/execute/route.ts

Fix E2B JavaScript and Python paths:

 // E2B JavaScript path (around line 745)
 for (const [k, v] of Object.entries(contextVariables)) {
-  prologue += `const ${k} = JSON.parse(${JSON.stringify(JSON.stringify(v))});\n`
+  // Convert undefined to null so JSON.stringify produces valid output
+  const safeValue = v === undefined ? null : v
+  prologue += `const ${k} = JSON.parse(${JSON.stringify(JSON.stringify(safeValue))});\n`
   prologueLineCount++
 }

 // E2B Python path (around line 817)
 for (const [k, v] of Object.entries(contextVariables)) {
-  prologue += `${k} = json.loads(${JSON.stringify(JSON.stringify(v))})\n`
+  // Convert undefined to null so JSON.stringify produces valid output
+  const safeValue = v === undefined ? null : v
+  prologue += `${k} = json.loads(${JSON.stringify(JSON.stringify(safeValue))})\n`
   prologueLineCount++
 }

File 3: apps/sim/lib/execution/isolated-vm-worker.cjs

Add defensive handling in worker:

 for (const [key, value] of Object.entries(contextVariables)) {
-  await jail.set(key, new ivm.ExternalCopy(value).copyInto())
+  // Convert undefined to null to ensure the variable is properly set in the VM
+  const safeValue = value === undefined ? null : value
+  await jail.set(key, new ivm.ExternalCopy(safeValue).copyInto())
 }

Why This Fix Works

This ensures:

  • All variables are transmitted via IPC (null is serializable, undefined is not)
  • Worker receives all variables and can inject them into the VM context
  • User code can use nullish checks (??) on the received null values

Related Issues

Testing

Verify with:

  1. Start trigger + Extract Context referencing non-existent webhook fields
  2. Confirm ?? operator fallback works correctly
  3. Test all three execution paths (E2B JS, E2B Python, Isolated-VM)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions