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

  1. Read the detail suffix after 'engine is poisoned:' — it names the original failure; fix that root cause
  2. Audit async operations for completion paths that can be skipped (timeouts, cancellation)
  3. Recreate/restart the engine instead of reusing a poisoned instance
  4. 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

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


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)