{"record":{"id":"151e6ac0911a921c","repo":"karatelabs/karate","slug":"settimeout-no-engine-is-executing-on-this-thread","errorCode":null,"errorMessage":"setTimeout: no engine is executing on this thread","messagePattern":"setTimeout: no engine is executing on this thread","errorType":"exception","errorClass":"JsErrorException","httpStatus":null,"severity":"error","filePath":"karate-js/src/main/java/io/karatelabs/js/AsyncSupport.java","lineNumber":729,"sourceCode":"\n    private static TimerScheduler scheduler;\n\n    private static synchronized TimerScheduler scheduler() {\n        if (scheduler == null || scheduler.isClosed() || scheduler.executor.isShutdown()) {\n            scheduler = new TimerScheduler(Executors.newSingleThreadScheduledExecutor(\n                    ThreadUtils.daemonFactory(\"js-timer-\", false)));\n            KarateLifecycle.register(scheduler);\n        }\n        return scheduler;\n    }\n\n    /** Owner resolution is from the call's own context, validated against the\n     *  thread's current engine — scheduling without a definite engine is an\n     *  error, not a guess. */\n    private static Engine timerEngine(Context context) {\n        Engine engine = context == null ? null : context.getEngine();\n        if (engine == null || engine != Engine.current()) {\n            throw JsErrorException.typeError(\"setTimeout: no engine is executing on this thread\");\n        }\n        return engine;\n    }\n\n    static Object setTimeout(Context context, Object[] args) {\n        Engine engine = timerEngine(context);\n        AsyncScope scope = engine.currentScope();\n        if (scope == null) {\n            throw JsErrorException.typeError(\"setTimeout requires an active evaluation\");\n        }\n        Object callback = arg(args, 0);\n        if (!(callback instanceof JsCallable target)) {\n            throw JsErrorException.typeError(\"setTimeout: callback is not a function\");\n        }\n        long delay = coerceDelay(arg(args, 1));\n        Object[] extra = args.length > 2 ? Arrays.copyOfRange(args, 2, args.length) : EMPTY_ARGS;\n        if (KarateLifecycle.phase() != KarateLifecycle.Phase.RUNNING) {\n            // refuse before a token is acquired — scheduling is about to be","sourceCodeStart":711,"sourceCodeEnd":747,"githubUrl":"https://github.com/karatelabs/karate/blob/a22eb90246d958d15a47bf436693d0121ad2812d/karate-js/src/main/java/io/karatelabs/js/AsyncSupport.java#L711-L747","documentation":"setTimeout validates that the calling context belongs to an Engine that is currently executing on the current thread (context.getEngine() != null and identical to Engine.current()). Scheduling a timer without a definite engine would leave the callback orphaned, so the library throws a TypeError rather than guessing.","triggerScenarios":"Calling setTimeout with a null Context, a context whose engine is null, or a context bound to an engine different from the one running on the current thread (e.g. setTimeout captured and called from another thread, or after the engine finished).","commonSituations":"Reusing a Context across threads in parallel step execution; calling setTimeout from a host callback that fires after evaluation ended; sharing contexts between two Engine instances.","solutions":["Call setTimeout only from code running inside the engine's own evaluation on the same thread.","Pass the Context from the active evaluation rather than a stale/captured one.","Move scheduling into JS code evaluated by the engine instead of calling the host binding directly."],"exampleFix":"// before: stale context on another thread\notherThreadContext.setTimeout(() -> ...);\n// after: schedule inside engine evaluation\nengine.evalRaw(\"setTimeout(() => done(), 100)\");","handlingStrategy":"validation","validationCode":"Engine current = Engine.current(); if (ctx == null || ctx.getEngine() == null || ctx.getEngine() != current) throw new IllegalStateException(\"setTimeout must run on the engine's own thread\");","typeGuard":null,"tryCatchPattern":"try { engine.evalRaw(\"setTimeout(cb, 100)\"); } catch (JsErrorException e) { if (e.getMessage().startsWith(\"setTimeout:\")) { /* fallback to Java scheduler */ } else throw e; }","preventionTips":["Never share a Context across threads.","Schedule timers from JS code evaluated by the engine itself.","Verify Engine.current() matches the context's engine before host-side timer calls."],"tags":["javascript","async","threading","timers"],"backgroundTag":"invalid-state-transition","analyzedSha":"a22eb90246d958d15a47bf436693d0121ad2812d","analyzedAt":"2026-09-12T09:01:00.220Z","contentChangedAt":"2026-09-12T09:01:00.220Z","schemaVersion":2},"datasetVersion":"2026-09-16T19:17:19.609Z"}