karatelabs/karate · critical · EngineException
engine is poisoned:
Error message
engine is poisoned:
What it means
The JS engine has been 'poisoned': a previous activation failed so badly (e.g. a stuck async activation) that the engine records a detail string and refuses all further work. checkPoisoned is called before waiting for scope ownership or jsLock so further evals fail fast instead of deadlocking behind the poisoned activation's locks.
Solutions
- Read the detail suffix after 'engine is poisoned:' — it names the original failure; fix that root cause
- Audit async operations for completion paths that can be skipped (timeouts, cancellation)
- Recreate/restart the engine instead of reusing a poisoned instance
- Check for callbacks that capture the engine and never return (deadlocks, missing resolves)
Example fix
// before
engine.eval("someStep()"); // throws 'engine is poisoned: ...'
// after
try {
engine.eval("someStep()");
} catch (EngineException e) {
// inspect e.getMessage() after 'engine is poisoned:' for the root cause
log.error(e.getMessage());
} Defensive patterns
Strategy: try-catch
Validate before calling
if (engine.isPoisoned()) { /* recreate engine instead of eval */ } Try / catch
try {
engine.eval(js);
} catch (EngineException e) {
String detail = e.getMessage().replaceFirst("^engine is poisoned: ", "");
// handle the root cause recorded in detail
} Prevention
- Always complete or time-bound async activations
- Fix the first failure — every later eval will report poisoned
- Restart the engine instead of reusing a poisoned instance
When it happens
Trigger: Any evalRaw/evalInternal/hostAsyncCall after a prior activation poisoned the engine — typically an async callback that never completed, a hung wait, or an unrecoverable internal error recorded in `poisoned`.
Common situations: A stuck async step (e.g. a timer or HTTP wait that never resolved) earlier in the run; subsequent evaluate calls then fail with 'engine is poisoned: <detail>'; the useful root cause is in the detail text, not in this throw site.
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.
Related errors
- Promise requires an engine
- Promise requires an active evaluation
- setTimeout: karate is shutting down
- setTimeout:
- async function called outside an engine evaluation
AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12).
Data as JSON: /api/errors/25a57549c0bc7497.
Report an issue: GitHub.
Appendix: source
Thrown at karate-js/src/main/java/io/karatelabs/js/Engine.java:218
/** True once this engine has been poisoned — see {@link #poisoned}. */
public boolean isPoisoned() {
return poisoned != null;
}
void poison(String detail) {
if (poisoned == null) {
poisoned = detail;
}
}
/** Checked before waiting for scope ownership or touching jsLock, so a
* poisoned engine fails fast rather than blocking on a lock its stuck
* activation may still hold. */
void checkPoisoned() {
String detail = poisoned;
if (detail != null) {
throw new EngineException("engine is poisoned: " + detail, null);
}
}
/** Fully release jsLock regardless of re-entrant hold count, returning the
* count so the caller can restore it exactly. */
int releaseJsLock() {
int holds = jsLock.getHoldCount();
for (int i = 0; i < holds; i++) {
jsLock.unlock();
}
return holds;
}
void reacquireJsLock(int holds) {
for (int i = 0; i < holds; i++) {
jsLock.lock();
}
}View on GitHub (pinned to a22eb90246)