{"record":{"id":"4cd24df22e8db75a","repo":"n8n-io/n8n","slug":"expression-exceeded-memory-limit-of-this-config","errorCode":null,"errorMessage":"Expression exceeded memory limit of ${this.config.memoryLimit}MB","messagePattern":"Expression exceeded memory limit of (.+?)MB","errorType":"exception","errorClass":"MemoryLimitError","httpStatus":null,"severity":"error","filePath":"packages/@n8n/expression-runtime/src/bridge/isolated-vm-bridge.ts","lineNumber":793,"sourceCode":"\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\t// Re-throw reconstructed errors as-is.\n\t\t\t// Note: TypeError is intentionally NOT included here — the isolate's\n\t\t\t// E() handler swallows TypeErrors (failed attack attempts return undefined),\n\t\t\t// so TypeErrors from host callbacks should also go through the generic\n\t\t\t// wrapping for consistent behavior.\n\t\t\tif (\n\t\t\t\terror instanceof Error &&\n\t\t\t\t(error.name === 'ExpressionError' || error.name === 'ExpressionExtensionError')\n\t\t\t) {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tconst errorMessage = error instanceof Error ? error.message : String(error);\n\t\t\tif (errorMessage.includes('Script execution timed out')) {\n\t\t\t\tthrow new TimeoutError(`Expression timed out after ${this.config.timeout}ms`, {});\n\t\t\t}\n\t\t\tif (errorMessage.includes('memory limit')) {\n\t\t\t\tthrow new MemoryLimitError(\n\t\t\t\t\t`Expression exceeded memory limit of ${this.config.memoryLimit}MB`,\n\t\t\t\t\t{},\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow new Error(`Expression evaluation failed: ${errorMessage}`);\n\t\t} finally {\n\t\t\tgetValueAtPath.release();\n\t\t\tgetArrayElement.release();\n\t\t\tcallHost.release();\n\t\t}\n\t}\n\n\t/**\n\t * Reconstruct an error from serialized isolate data.\n\t *\n\t * Maps error names back to their host-side classes and restores\n\t * custom properties that would otherwise be lost crossing the boundary.\n\t */","sourceCodeStart":775,"sourceCodeEnd":811,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/expression-runtime/src/bridge/isolated-vm-bridge.ts#L775-L811","documentation":"Thrown as MemoryLimitError by IsolatedVmBridge.execute() when the isolate aborts with a message containing 'memory limit'. The isolate has a fixed memory ceiling (config.memoryLimit, default 128MB via DEFAULT_BRIDGE_CONFIG); allocations beyond it abort the script. MemoryLimitError extends ExpressionError and maps to the 'memory_limit' observability bucket. Critically, an OOM disposes the underlying isolate — subsequent expressions on the same caller will fail with error 310.","triggerScenarios":"An expression allocates beyond the isolate heap: building a giant array (e.g. let a=[]; while(true){a.push(new Array(1000000).fill(1))}, the test fixture), unbounded string concatenation, or materializing a very large $json. Reproduced in tests with memoryLimit:8.","commonSituations":"A user expression that accidentally expands a large dataset (e.g. mapping a huge array into a bigger structure). memoryLimit configured too low for legitimate data shapes. A bug causing unbounded growth in a loop. Processing an attachment/log file inline in an expression.","solutions":["Catch MemoryLimitError and report it; also expect the caller's isolate to be dead (see error 310) and acquire a fresh one.","Refactor the expression to avoid materializing large structures — process in a Code node or stream.","Raise config.memoryLimit if the data legitimately exceeds 128MB (balance against host memory).","Audit observability 'memory_limit' events to find which expression/node is the offender."],"exampleFix":"// before — materialize a huge array in an expression\nconst result = evaluator.evaluate('{{ $json.bigRows.map(r => ({...r, x: heavy(r)})) }}', data, caller);\n// after — defer to a node, and handle the dead isolate\ntry {\n  const result = evaluator.evaluate('{{ $json.bigRows.length }}', data, caller);\n} catch (e) {\n  if (e instanceof MemoryLimitError) {\n    await evaluator.release(caller); // isolate is dead; drop it\n    // route the heavy work to a Code/Function node instead\n  }\n}","handlingStrategy":"try-catch","validationCode":"// Bound input size before evaluating to reduce OOM risk\nfunction isSafeToEvaluate(data, maxItems = 100000) {\n  const rows = data?.$json?.rows ?? data?.$json;\n  return !Array.isArray(rows) || rows.length <= maxItems;\n}","typeGuard":"import { MemoryLimitError } from '@n8n/expression-runtime';\nfunction isMemoryLimitError(e) { return e instanceof MemoryLimitError || e?.name === 'MemoryLimitError'; }","tryCatchPattern":"import { MemoryLimitError } from '@n8n/expression-runtime';\ntry {\n  const result = evaluator.evaluate(expr, data, caller);\n} catch (e) {\n  if (e instanceof MemoryLimitError) {\n    // isolate is now dead for this caller; release it and fail/recover the execution\n    await evaluator.release(caller);\n  } else throw e;\n}","preventionTips":["Do not materialize large arrays/strings inside expressions — process them in a Code node.","Set config.memoryLimit deliberately (default 128MB) based on host memory and workload.","After a MemoryLimitError, release the caller's bridge (it is dead) — see error 310.","Monitor the 'memory_limit' observability bucket for repeat offenders."],"tags":["expression-runtime","isolate","memory","resource-limit","oom"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}