{"record":{"id":"11d3dff9b095ec45","repo":"karatelabs/karate","slug":"settimeout-karate-is-shutting-down","errorCode":null,"errorMessage":"setTimeout: karate is shutting down","messagePattern":"setTimeout: karate is shutting down","errorType":"exception","errorClass":"EngineInterruptedException","httpStatus":null,"severity":"error","filePath":"karate-js/src/main/java/io/karatelabs/js/AsyncSupport.java","lineNumber":749,"sourceCode":"        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\n            // rejected anyway, and an unbacked token would strand the drain\n            throw new EngineInterruptedException(\"setTimeout: karate is shutting down\");\n        }\n        AsyncToken token = scope.acquire(\"timer\");\n        if (token == null) {\n            throw new EngineInterruptedException();\n        }\n        AsyncActivation.anyAsync = true;\n        int id = scope.nextTimerId();\n        AsyncScope.TimerRecord record = new AsyncScope.TimerRecord(scope, id, token);\n        scope.addTimer(record);\n        TimerScheduler timer = scheduler();\n        // the closed-check and the registration are one atomic step under the\n        // scheduler's gate — see TimerScheduler for why ordering alone would not do\n        if (!timer.register(record)) {\n            scope.removeTimer(id);\n            retireTimer(record);\n            throw new EngineInterruptedException(\"setTimeout: karate is shutting down\");\n        }\n        ScheduledFuture<?> future;","sourceCodeStart":731,"sourceCodeEnd":767,"githubUrl":"https://github.com/karatelabs/karate/blob/a22eb90246d958d15a47bf436693d0121ad2812d/karate-js/src/main/java/io/karatelabs/js/AsyncSupport.java#L731-L767","documentation":"karate-js's setTimeout refuses to schedule a new async timer once the Karate lifecycle has left the RUNNING phase. Throwing EngineInterruptedException here prevents an unbacked AsyncToken from being created that could never be drained during shutdown. It is a deliberate fail-fast guard, not a bug.","triggerScenarios":"Calling setTimeout (e.g. from JS `setTimeout(fn, delay)` or Java host code via hostAsyncCall) while KarateLifecycle.phase() is not RUNNING — i.e. before a run starts, during teardown, or after a suite has been aborted/cancelled.","commonSituations":"Background timers kicked off from after-scenario hooks or cleanup code; a JS callback that schedules a follow-up timer while the engine is being torn down; test-parallel worker shutdown racing a pending setTimeout call.","solutions":["Move the setTimeout call into running scenario/feature code so the lifecycle is in RUNNING phase","Guard the call: check KarateLifecycle.phase() == RUNNING before scheduling","Replace fire-and-forget timers with synchronous logic or Karate's built-in async/delay mechanisms","If shutdown is unexpected, check for earlier failures (cancellation, thread interruption) that moved the lifecycle out of RUNNING"],"exampleFix":"// before\nsetTimeout(() => doWork(), 100); // may throw during teardown\n// after\nif (KarateLifecycle.phase() === 'RUNNING') {\n  setTimeout(() => doWork(), 100);\n}","handlingStrategy":"validation","validationCode":"if (KarateLifecycle.phase() !== KarateLifecycle.Phase.RUNNING) {\n  // skip or defer the timer\n  return;\n}\nsetTimeout(fn, delay);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only schedule timers from within running scenario code","Avoid timers in afterScenario/afterFeature hooks","Check lifecycle phase before async scheduling"],"tags":["javascript","async","shutdown","lifecycle","timer"],"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"}