n8n-io/n8n · error · IsolateError
Evaluator disposed
Error message
Evaluator disposed
What it means
Thrown as IsolateError('Evaluator disposed') by ExpressionEvaluator.evaluate() when this.disposed is true — i.e. evaluate() is called after dispose(). dispose() sets the flag, clears the code cache, and disposes the bridge pool; any subsequent evaluate() is a use-after-dispose bug. IsolateError is imported from @n8n/errors.
Source
Thrown at packages/@n8n/expression-runtime/src/evaluator/expression-evaluator.ts:113
try {
bridge = this.pool.acquire();
} catch (error) {
if (error instanceof PoolDisposedError) throw error;
if (!(error instanceof PoolExhaustedError)) throw error;
bridge = await this.createBridge();
}
this.config.observability?.metrics.counter(EXPRESSION_METRICS.poolAcquired.name, 1);
this.bridgesByCaller.set(caller, bridge);
return true;
}
evaluate(
expression: string,
data: WorkflowData,
caller: object,
options?: EvaluateOptions,
): unknown {
if (this.disposed) throw new IsolateError('Evaluator disposed');
const bridge = this.getBridge(caller);
// Transform template expression → sanitized JavaScript (cached)
const transformedCode = this.getTransformedCode(expression);
const { observability } = this.config;
const start = performance.now();
try {
const result = bridge.execute(transformedCode, data, {
timezone: options?.timezone,
});
recordOutcome(observability, start, 'success');
return result;
} catch (error) {
recordOutcome(observability, start, 'error', error);
throw error;View on GitHub (pinned to 5ac6606e81)
Solutions
- Check evaluator.isDisposed() before calling evaluate() in code paths that may outlive the evaluator.
- Ensure dispose() is only called after all in-flight evaluations have completed (drain before dispose).
- In tests, await pending evaluations before invoking evaluator.dispose() in afterEach.
- Give each long-lived consumer its own evaluator, or use reference counting, so one consumer's dispose doesn't break others.
Example fix
// before — dispose races an in-flight evaluation
evaluator.dispose();
const result = evaluator.evaluate('{{ $json.x }}', data, caller); // throws
// after — guard with isDisposed
if (!evaluator.isDisposed()) {
const result = evaluator.evaluate('{{ $json.x }}', data, caller);
} Defensive patterns
Strategy: validation
Validate before calling
function evaluateIfAlive(evaluator, expr, data, caller) {
if (evaluator.isDisposed()) throw new Error('Evaluator is disposed; create a new one');
return evaluator.evaluate(expr, data, caller);
} Type guard
function isEvaluatorUsable(evaluator) { return !evaluator.isDisposed(); } Try / catch
import { IsolateError } from '@n8n/errors';
try {
const result = evaluator.evaluate(expr, data, caller);
} catch (e) {
if (e instanceof IsolateError && /disposed/i.test(e.message)) {
// create a fresh evaluator or skip this evaluation
} else throw e;
} Prevention
- Check evaluator.isDisposed() before calling evaluate() in long-lived or async paths.
- Drain all in-flight evaluations before calling dispose().
- In tests, await pending work before evaluator.dispose() in afterEach.
- Avoid sharing one evaluator across consumers with different lifecycles.
When it happens
Trigger: Calling evaluator.evaluate(expression, data, caller) after await evaluator.dispose() has run. Common in shutdown races: a workflow finalizer disposes the evaluator while an in-flight expression is still being evaluated, or a test tears down the evaluator before an async evaluation completes.
Common situations: Test teardown disposing the evaluator before pending evaluations finish. Application shutdown disposing shared evaluator while workers still reference it. A bug where dispose() is called on the wrong lifecycle event.
Related errors
- Isolate for this caller is no longer available
- ${toolName} requires resumeSubAgent and cancelSubAgent to be
- Filesystem "${this.id}" is not ready (status: ${this.status}
- Sandbox "${this.name}" has been destroyed
- Sandbox "${this.name}" failed to start (status: ${this.statu
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/410b50806295608a.
Report an issue: GitHub.