karatelabs/karate · error · RuntimeException

callSingle failed

Error message

callSingle failed: <path> - <failureMsg>

What it means

When karate.callSingle() invokes a feature and that child FeatureRuntime fails, executeCallSingle collects the first failed ScenarioResult's failure message and throws a RuntimeException 'callSingle failed: <path> - <message>'. This re-throws the underlying feature failure to the caller (and caches the exception for subsequent callSingle invocations of the same path).

Solutions

  1. Run the called feature standalone (Runner.path) to see the full failure and fix the root cause shown after the ' - '.
  2. Check environment/config the shared feature depends on (URLs, credentials) — callSingle features often fail before any test logic runs.
  3. Note the exception is cached: fix the feature and restart the suite; retrying within the same run returns the cached failure.
  4. If only JS is needed, prefer a .js file over a feature for callSingle to avoid full scenario execution overhead.

Example fix

// before: auth.feature fails
* def token = karate.callSingle('classpath:auth.feature').token
// -> callSingle failed: classpath:auth.feature - http call failed ...

// after: harden the shared feature
* configure retry = { count: 3, interval: 2000 }
* def token = karate.call('classpath:auth.feature').token
Defensive patterns

Strategy: try-catch

Validate before calling

// validate the shared feature in a pre-flight check at suite start:
// Runner.path("classpath:auth.feature").parallel(1) in a build step before the suite

Try / catch

try {
    var token = karate.callSingle('classpath:auth.feature').token;
} catch (e) {
    if (String(e).indexOf('callSingle failed') >= 0) {
        karate.log('shared feature failed; cause: ' + e);
        throw e; // usually a genuine env/config failure — don't mask
    }
    throw e;
}

Prevention

When it happens

Trigger: karate.callSingle('some.feature') where the called feature has a failing scenario — a failed step, assertion, or setup error. The reported message is the first failed scenario's failure text.

Common situations: Shared data-provision features (auth token fetch, seed data) failing due to environment issues; a broken helper feature reused across the suite so every callSingle consumer fails; parallel runs surfacing the cached exception repeatedly.

Related errors


AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12). Data as JSON: /api/errors/2067776d7db2087e. Report an issue: GitHub.

Appendix: source

Thrown at karate-core/src/main/java/io/karatelabs/core/ScenarioRuntime.java:854

            );
            FeatureResult fr = nestedFr.call();

            // Attach the called feature's result so its steps (and HTTP traffic) surface
            // in the calling scenario's HTML / Cucumber JSON / JUnit report. callSingle
            // executes exactly once per Suite under a lock — only the "winning" scenario
            // (cache miss) reaches this code, so the call results appear under whichever
            // scenario's thread actually ran the feature. Done before the failure throw
            // so a failed callSingle still surfaces its steps in the report.
            executor.addCallResult(fr);

            // Check if the feature failed
            if (fr.isFailed()) {
                String failureMsg = fr.getScenarioResults().stream()
                        .filter(ScenarioResult::isFailed)
                        .findFirst()
                        .map(ScenarioResult::getFailureMessage)
                        .orElse("callSingle feature failed");
                throw new RuntimeException("callSingle failed: " + path + " - " + failureMsg);
            }

            if (nestedFr.getLastExecuted() != null) {
                // Cache only the delta — the variables the called feature actually added or
                // replaced — not its full binding set. A called feature inherits the caller's
                // visible variables, so the full set echoes back caller state: Scenario Outline
                // example-row columns (frozen to row 1 and replayed onto later rows via a
                // karate.set(...) spread — #2934) and config-level refs like Java.type(...)
                // (serialized into the disk cache, then deserialized as broken values that
                // clobber the live ref on a warm run — #2933). Diffed against what the callee
                // was actually seeded with (isolated scope shallow-copies maps and lists, so
                // comparing against the caller's own live vars read every inherited map as
                // "replaced" and leaked it back) — the same helper the call keyword uses.
                return StepExecutor.calleeResult(nestedFr.getLastExecuted());
            }
            return new HashMap<>();
        } else if (content instanceof JavaCallable) {
            // JavaScript function - invoke it with the arg

View on GitHub (pinned to a22eb90246)