karatelabs/karate · error · EngineException
await on a promise that can never settle
Error message
await on a promise that can never settle
What it means
While pumping jobs waiting for a promise, if the scope becomes quiescent (no pending jobs, nothing to run) but the promise is still unsettled, AsyncSupport throws EngineException 'await on a promise that can never settle' — no remaining work could ever resolve it, so waiting longer is pointless.
Solutions
- Ensure every promise has a code path that calls resolve/reject regardless of control flow
- Verify you await the correct promise instance and that its producer was actually scheduled before awaiting
- Reproduce with logging in the promise executor to confirm whether the executor body ever runs
- Catch EngineException, inspect the promise creation site, and restructure so settlement is owned by scheduled scope jobs
Example fix
// before (JS)
let p = new Promise(() => {}); // executor never settles
await p;
// after (JS)
let p = new Promise((resolve) => setTimeout(() => resolve(42), 10));
await p; // always settles Defensive patterns
Strategy: try-catch
Validate before calling
// JS: wrap promise creation so accidental never-settling executors throw in dev
let p = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('producer did not settle')), 30_000); // watchdog
}); Try / catch
try {
Object result = engine.await(promise);
} catch (EngineException e) {
if (e.getMessage() != null && e.getMessage().contains("never settle")) {
throw new ScriptBugException("promise producer never settles — fix the producer, not the await");
}
throw e;
} Prevention
- Never leave a promise executor without calling resolve or reject
- Only await promises whose producers were scheduled in the same live scope
- Add a watchdog timeout inside long-lived promises during development
- After scope teardown, do not create or await new promises
When it happens
Trigger: Calling await(promise) where the promise was created but its resolving job already ran without settling it, resolution depends on a callback that was never registered, or the promise's producer was dropped during scope teardown.
Common situations: Promise constructed with a resolve captured in a closure that is never invoked; awaiting a promise after the engine/scope has finished its jobs (e.g. post-teardown code); races where the settle happens on a thread not owned by the scope.
Related errors
- async drain timed out
- Promise requires an engine
- Promise requires an active evaluation
- unhandled promise rejection:
- unhandled promise rejection
AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12).
Data as JSON: /api/errors/2636bbb101e757e1.
Report an issue: GitHub.
Appendix: source
Thrown at karate-js/src/main/java/io/karatelabs/js/AsyncSupport.java:377
// On an abrupt exit the lock is deliberately NOT reacquired: a running
// activation may hold it, and this thread is on its way to tearing the
// scope down. Every enclosing frame unlocks conditionally for that reason.
while (!promise.isSettled()) {
if (Thread.currentThread().isInterrupted() || scope.isCancelRequested()) {
throw new EngineInterruptedException();
}
if (deadlineNanos != 0 && System.nanoTime() > deadlineNanos) {
throw new EngineTimeoutException("async drain timed out");
}
AsyncJob job = scope.takeJob(POLL_MILLIS);
if (job instanceof AsyncJob.Control control) {
job.token.release();
throw new EngineInterruptedException(control.reason);
}
if (job != null) {
runJob(engine, job);
} else if (scope.isQuiescent() && !promise.isSettled()) {
throw new EngineException("await on a promise that can never settle", null);
}
}
engine.reacquireJsLock(holds);
return settledResult(promise);
}
//=== adoption =====================================================================================================
/**
* The one adoption operation, shared by the {@code Promise} constructor's
* resolve, {@code Promise.resolve}, async-function return, {@code .then}
* results and {@code CompletionStage} wrapping. JS-level rules run before
* any CF composition.
*/
static void resolveValue(JsPromise target, Object value, AsyncToken retiring) {
if (value == target) {
target.settle(true, JsErrorException.typeError("Chaining cycle detected for promise").payload, retiring);
return;View on GitHub (pinned to a22eb90246)