karatelabs/karate · error · JsErrorException
setTimeout requires an active evaluation
Error message
setTimeout requires an active evaluation
What it means
setTimeout requires not only a live engine on the thread but an active AsyncScope evaluation. The scope is what tracks pending timers and drives the drain loop; with no scope in progress there is nothing to attach the timer to, so the call is rejected with a TypeError.
Solutions
- Schedule timers only from within an async evaluation (e.g. inside an async function invoked through the engine's async path).
- If you need delayed work in sync code, perform it on the Java side instead of via setTimeout.
- Ensure the engine's currentScope() is active — don't defer setTimeout calls past the end of the evaluation.
Example fix
// before: sync eval
evalRaw("setTimeout(cb, 50)"); // throws
// after: async evaluation
await engine.evalRaw("(async () => { await delay(50); cb(); })()"); Defensive patterns
Strategy: validation
Validate before calling
if (engine.currentScope() == null) throw new IllegalStateException("setTimeout needs an active async evaluation"); Try / catch
try { engine.evalRaw("setTimeout(cb, 50)"); } catch (JsErrorException e) { if (e.getMessage().equals("setTimeout requires an active evaluation")) { /* do the delayed work in Java */ } else throw e; } Prevention
- Use setTimeout only inside async evaluations (async functions / async blocks).
- Don't defer setTimeout past the end of the evaluation that owns the scope.
- For synchronous scripts, implement delays on the Java side.
When it happens
Trigger: Calling setTimeout from engine code executing outside an async evaluation — e.g. a synchronous top-level eval where no AsyncScope was pushed, or after the async scope completed.
Common situations: Using setTimeout in a plain synchronous JS evaluation (no await/async root); calling it during host-driven evaluation that didn't open an async scope; timers scheduled after the async block already returned.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- setTimeout: no engine is executing on this thread
- setTimeout: karate is shutting down
- setTimeout:
- engine is poisoned:
- async function called outside an engine evaluation
AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12).
Data as JSON: /api/errors/a2b79a9abf7be851.
Report an issue: GitHub.
Appendix: source
Thrown at karate-js/src/main/java/io/karatelabs/js/AsyncSupport.java:738
return scheduler;
}
/** Owner resolution is from the call's own context, validated against the
* thread's current engine — scheduling without a definite engine is an
* error, not a guess. */
private static Engine timerEngine(Context context) {
Engine engine = context == null ? null : context.getEngine();
if (engine == null || engine != Engine.current()) {
throw JsErrorException.typeError("setTimeout: no engine is executing on this thread");
}
return engine;
}
static Object setTimeout(Context context, Object[] args) {
Engine engine = timerEngine(context);
AsyncScope scope = engine.currentScope();
if (scope == null) {
throw JsErrorException.typeError("setTimeout requires an active evaluation");
}
Object callback = arg(args, 0);
if (!(callback instanceof JsCallable target)) {
throw JsErrorException.typeError("setTimeout: callback is not a function");
}
long delay = coerceDelay(arg(args, 1));
Object[] extra = args.length > 2 ? Arrays.copyOfRange(args, 2, args.length) : EMPTY_ARGS;
if (KarateLifecycle.phase() != KarateLifecycle.Phase.RUNNING) {
// refuse before a token is acquired — scheduling is about to be
// rejected anyway, and an unbacked token would strand the drain
throw new EngineInterruptedException("setTimeout: karate is shutting down");
}
AsyncToken token = scope.acquire("timer");
if (token == null) {
throw new EngineInterruptedException();
}
AsyncActivation.anyAsync = true;
int id = scope.nextTimerId();View on GitHub (pinned to a22eb90246)