n8n-io/n8n · error · TimeoutError
Expression timed out after
Error message
Expression timed out after ${this.config.timeout}ms What it means
Thrown as TimeoutError by IsolatedVmBridge.execute() when the V8 isolate aborts the script with a message containing 'Script execution timed out'. The isolate is given a per-evaluation timeout (config.timeout, default 5000ms via DEFAULT_BRIDGE_CONFIG); exceeding it terminates the script. TimeoutError extends ExpressionError and is the classified 'timeout' bucket in observability. The isolate itself survives (unlike OOM), but the offending expression is aborted.
Solutions
- Catch TimeoutError specifically and surface a clear message to the workflow author.
- Raise config.timeout if the expression legitimately needs more time (weigh against DoS risk).
- Refactor the expression to avoid loops / catastrophic regex; move heavy work into a Code node.
- If using the evaluator, the error is classified as 'timeout' in metrics — check observability for recurring offenders.
Example fix
// before — infinite loop in an expression
const result = evaluator.evaluate('while(true){}', data, caller);
// after — bounded work, and guard the call
try {
const result = evaluator.evaluate('{{ $json.items.slice(0, 100).length }}', data, caller);
} catch (e) {
if (e instanceof TimeoutError) {
// tell the author their expression is too slow / looping
}
} Defensive patterns
Strategy: try-catch
Validate before calling
// You cannot fully prevent an arbitrary expression from looping, but you can bound work
// before evaluation:
function isBoundedExpression(expr) {
return !/\bwhile\s*\(\s*(?:true|1|!!1)\s*\)\b/.test(expr) && !/\bfor\s*\(\s*;;\s*\)\b/.test(expr);
} Type guard
import { TimeoutError } from '@n8n/expression-runtime';
function isTimeoutError(e) { return e instanceof TimeoutError || e?.name === 'TimeoutError'; } Try / catch
import { TimeoutError } from '@n8n/expression-runtime';
try {
const result = evaluator.evaluate(expr, data, caller);
} catch (e) {
if (e instanceof TimeoutError) {
// expression ran > config.timeout ms (default 5000)
// refactor the expression or raise config.timeout
} else throw e;
} Prevention
- Avoid while(true) and unbounded loops in expressions; move heavy logic to a Code node.
- Set config.timeout deliberately (default 5000ms) — high enough for legitimate work, low enough to bound DoS.
- Watch for catastrophic-backtracking regexes inside expressions.
- Monitor the 'timeout' observability bucket to find slow expressions.
When it happens
Trigger: An n8n expression evaluated in the isolate runs longer than config.timeout: an infinite or near-infinite loop (while(true){}), a very expensive computation over a large array, or a deeply recursive user expression. Reproduced in tests with bridge.execute('while(true){}', {}) at timeout:100.
Common situations: A user-authored expression with a bug causing an infinite loop. Processing a huge array with a naive O(n^2) expression. A regex with catastrophic backtracking inside an expression. timeout configured too low for legitimate heavy expressions.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Expression exceeded memory limit of
- Expression evaluation failed
- Isolate for this caller is no longer available
- 500
- arguments must be passed to pluck
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/a584e78ed56c3973.
Report an issue: GitHub.
Appendix: source
Thrown at packages/@n8n/expression-runtime/src/bridge/isolated-vm-bridge.ts:790
this.logger.debug('[IsolatedVmBridge] Expression executed successfully');
return result;
} catch (error) {
// Re-throw reconstructed errors as-is.
// Note: TypeError is intentionally NOT included here — the isolate's
// E() handler swallows TypeErrors (failed attack attempts return undefined),
// so TypeErrors from host callbacks should also go through the generic
// wrapping for consistent behavior.
if (
error instanceof Error &&
(error.name === 'ExpressionError' || error.name === 'ExpressionExtensionError')
) {
throw error;
}
const errorMessage = error instanceof Error ? error.message : String(error);
if (errorMessage.includes('Script execution timed out')) {
throw new TimeoutError(`Expression timed out after ${this.config.timeout}ms`, {});
}
if (errorMessage.includes('memory limit')) {
throw new MemoryLimitError(
`Expression exceeded memory limit of ${this.config.memoryLimit}MB`,
{},
);
}
throw new Error(`Expression evaluation failed: ${errorMessage}`);
} finally {
getValueAtPath.release();
getArrayElement.release();
callHost.release();
}
}
/**
* Reconstruct an error from serialized isolate data.
*View on GitHub (pinned to 5ac6606e81)