windmill-labs/windmill · error
The expression evaluation `{expr}` took too long to execute
Error message
The expression evaluation `{expr}` took too long to execute (>{EVAL_TIMEOUT_MS}ms) What it means
A flow step-input transform expression evaluated with QuickJS exceeded the hard wall-clock timeout (EVAL_TIMEOUT_MS). The tokio select/timed wrapper aborted the evaluation and returned this error instead of the expression result.
Source
Thrown at backend/windmill-jseval/src/lib.rs:325
handle.block_on(async move {
eval_quickjs_inner(
&expr_clone,
filtered_context,
flow_input_clone,
flow_env_clone,
authed_client_clone,
by_id_clone,
ctx,
context_keys,
memory_limit,
)
.await
})
}),
)
.await
.map_err(|_| {
anyhow::anyhow!(
"The expression evaluation `{expr}` took too long to execute (>{EVAL_TIMEOUT_MS}ms)"
)
})??
}
/// Default memory cap, in bytes, for a single flow step-input transform eval
/// (`eval_timeout_quickjs`). Large enough for transforms that build sizeable
/// arrays; genuinely large payloads raise it via `QUICKJS_MEMORY_LIMIT_MB`.
///
/// Sizing constraint: evals are authenticated and, within a worker process, run
/// one at a time (`transform_input` awaits each transform sequentially and each
/// eval drops its runtime before the next), so in the default one-worker-per-
/// process deployment the peak is a single cap. Native / multi-worker-in-process
/// mode runs up to `NUM_WORKERS` evals concurrently in one heap, so the peak is
/// `NUM_WORKERS × cap` — hence a modest default rather than a large one.
#[cfg(feature = "quickjs")]
const DEFAULT_QUICKJS_MEMORY_LIMIT_BYTES: usize = 128 * 1024 * 1024;
View on GitHub (pinned to e474e8803c)
Solutions
- Reduce the amount of data the expression handles — filter/map earlier in the flow instead of inside the transform
- Rewrite the expression to avoid unbounded loops and quadratic operations
- Move heavy processing into a proper script step rather than a step-input transform
- Raise the eval timeout via the EVAL_TIMEOUT_MS environment variable if legitimately slow but bounded
Example fix
// before results.items.map(x => expensiveTransform(x)) // O(n^2), times out // after results.items.filter(x => x.keep).map(x => expensiveTransform(x))
Defensive patterns
Strategy: try-catch
Validate before calling
// bound work before submitting the expression
if (results.items.length > 10000) throw new Error('too many items for transform'); Try / catch
try {
const out = await evalExpression(expr, inputs);
} catch (e) {
if (String(e).includes('took too long to execute')) {
// fall back to a dedicated script step instead of the transform
}
throw e;
} Prevention
- Avoid unbounded loops in transform expressions
- Filter large arrays before transforming
- Keep transforms trivial; move heavy work into script steps
- Tune EVAL_TIMEOUT_MS only when evaluations are legitimately long
When it happens
Trigger: An expression like `JSON.stringify(results.step)` on a very large payload, an accidental infinite loop (e.g. `while(true)` or O(n^2) mapping over a big array) inside a step-input transform, or a transform referencing a huge previous-step result that takes longer than the timeout to process.
Common situations: Passing megabyte-scale payloads through flow input transforms; authoring a transform with an unbounded loop; a slow string-building expression over thousands of rows.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- The expression evaluation exceeded the memory limit of {} MB
- QuickJS evaluation error: {}
- The expression evaluation took too long to execute (>{EVAL_T
- Timed out waiting for flow ${id} to complete
- Timed out waiting for job ${id}
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/76f56fa5f12698f2.
Report an issue: GitHub.