{"record":{"id":"3f6d141da7c28641","repo":"karatelabs/karate","slug":"karate-callonce-requires-a-feature-context","errorCode":null,"errorMessage":"karate.callonce() requires a feature context","messagePattern":"karate\\.callonce\\(\\) requires a feature context","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"karate-core/src/main/java/io/karatelabs/core/ScenarioRuntime.java","lineNumber":634,"sourceCode":"            if (arg instanceof Map) {\n                callArg = (Map<String, Object>) arg;\n            } else {\n                throw new RuntimeException(\"karate.call() arg must be a map or list, got: \" + arg.getClass());\n            }\n        }\n\n        Map<String, Object> resultVars = executor.callFeatureSingle(calledFeature, callArg, tagSelector, lineFilters);\n        return resultVars != null ? resultVars : new HashMap<>();\n    }\n\n    /**\n     * Execute karate.callonce() - runs a feature once per FeatureRuntime and caches the result.\n     * Uses the same cache as the callonce keyword.\n     * Uses double-check locking to ensure thread-safe execution in parallel scenarios.\n     */\n    public Object executeJsCallOnce(String path, Object arg) {\n        if (featureRuntime == null) {\n            throw new RuntimeException(\"karate.callonce() requires a feature context\");\n        }\n\n        // Use the same cache key format as the keyword: \"callonce:call read('path')\"\n        String cacheKey = \"callonce:call read('\" + path + \"')\";\n\n        // Use feature-level cache (not suite-level) - callOnce is scoped per feature\n        Map<String, Object> cache = featureRuntime.getCallOnceCache();\n        java.util.concurrent.locks.ReentrantLock lock = featureRuntime.getCallOnceLock();\n\n        // Fast path - check cache without lock\n        Object cached = cache.get(cacheKey);\n        if (cached != null) {\n            // Deep copy to prevent cross-scenario mutation\n            return StepUtils.deepCopy(cached);\n        }\n\n        // Slow path - acquire lock for execution\n        lock.lock();","sourceCodeStart":616,"sourceCodeEnd":652,"githubUrl":"https://github.com/karatelabs/karate/blob/a22eb90246d958d15a47bf436693d0121ad2812d/karate-core/src/main/java/io/karatelabs/core/ScenarioRuntime.java#L616-L652","documentation":"executeJsCallOnce implements karate.callonce(), running a feature once per FeatureRuntime with a shared cache (double-check locking for parallel safety). It requires a feature context because the cache and the child call are feature-scoped; if featureRuntime is null this RuntimeException is thrown.","triggerScenarios":"Calling karate.callonce('path.feature', arg) from JS outside any feature execution — e.g. callSingle-cached code, karate-config.js, or standalone JS evaluation without a feature run.","commonSituations":"Nesting callonce inside callSingle utilities; using callonce from a karate-config.js bootstrap script; running Karate JS via embedded engine without Runner.","solutions":["Use karate.callonce() only within scenario/Background JS of a running feature.","For suite-wide one-time calls use karate.callSingle() instead, which has Suite context.","Ensure the calling feature is executed through the Karate Runner so a FeatureRuntime exists.","If shared data is needed before features run, compute it in callSingle JS and pass it as a system property/config variable."],"exampleFix":"// before: inside callSingle JS\nvar token = karate.callonce('classpath:auth.feature').authToken;\n\n// after: inside the feature\n* def token = karate.callonce('classpath:auth.feature').authToken","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function callOnceSafe(path, arg) {\n  try { return karate.callonce(path, arg); }\n  catch (e) {\n    if (String(e).indexOf('requires a feature context') >= 0) return null;\n    throw e;\n  }\n}","tryCatchPattern":"try {\n    var token = karate.callonce('classpath:auth.feature').authToken;\n} catch (e) {\n    if (String(e).indexOf('requires a feature context') >= 0) {\n        throw new Error('karate.callonce used outside a feature — move it into a scenario');\n    }\n    throw e;\n}","preventionTips":["Use callonce only in scenario/Background JS","Use karate.callSingle() when suite-level caching is needed","Don't nest callonce inside callSingle utilities","Ensure features are launched via Runner so FeatureRuntime exists"],"tags":["callonce","karate-js","caching","lifecycle"],"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"}