karatelabs/karate · error · RuntimeException

image: failed to read options

Error message

image: failed to read options '{optionsPath}': {message}

What it means

When diff() resolves a named baseline it loads the companion options JSON file. A missing options file is silently ignored, but if the file exists and cannot be read or parsed as JSON, loadOptionsFile wraps the failure in a RuntimeException naming the options path and underlying message.

Solutions

  1. Open the optionsPath printed in the message and validate it is well-formed JSON (run it through a JSON linter)
  2. Fix file permissions so the test process can read it
  3. If the options are unnecessary, delete the file — a missing options file is treated as empty
  4. Re-establish the baseline with image.write so a fresh, valid options file is written

Example fix

// before (options file, invalid)
{ "fuzzy": 0.1, }
// after
{ "fuzzy": 0.1 }
Defensive patterns

Strategy: try-catch

Validate before calling

# before calling image.diff, sanity-check the options file
* def optsText = karate.readIfExists(optionsPath) || ''
* if (optsText != '') eval "JSON.parse(karate.string(optsText))" // throws early on invalid JSON

Try / catch

try { return karate.call('image.diff', { name: name }); } catch (Exception e) { if (('' + e).startsWith('image: failed to read options')) { karate.warn('bad options file, diffing with defaults'); return karate.call('image.diff', { name: name, optionsRemoved: true }); } throw e; }

Prevention

When it happens

Trigger: The <name>.options.json (or similarly resolved optionsPath) exists but contains invalid JSON, is unreadable due to permissions, or the stream fails mid-read while calling image.diff with a name.

Common situations: Hand-edited options file with a trailing comma or comments; options file saved with wrong encoding or as HTML (error page) by a misconfigured process; file locked or chmod'ed 000 in CI; partial file from an interrupted write.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


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

Appendix: source

Thrown at karate-image/src/main/java/io/karatelabs/ext/image/ImageApi.java:384

    private String optionsDir() {
        String dir = str(config.get("optionsDir"));
        return dir != null ? dir : str(config.get("baselineDir"));
    }

    // ---- options file ----

    private Map<String, Object> loadOptionsFile(String optionsPath) {
        if (optionsPath == null) {
            return new LinkedHashMap<>();
        }
        Resource r = resolveResource(optionsPath);
        if (r == null || !r.exists()) {
            return new LinkedHashMap<>();   // missing options is never an error
        }
        try (InputStream is = r.getStream()) {
            return Json.of(new String(is.readAllBytes(), StandardCharsets.UTF_8)).asMap();
        } catch (Exception e) {
            throw new RuntimeException("image: failed to read options '" + optionsPath + "': " + e.getMessage(), e);
        }
    }

    /** Suite-level engine config passed to {@link ImageComparison} as defaultOptions. */
    private Map<String, Object> defaultOptions() {
        Map<String, Object> d = new LinkedHashMap<>();
        d.put("engine", config.getOrDefault("engine", "resemble"));
        if (config.containsKey("threshold")) {
            d.put("failureThreshold", config.get("threshold"));
        }
        d.put("report", config.getOrDefault("report", "mismatched"));
        if (config.containsKey("allowScaling")) {
            d.put("allowScaling", config.get("allowScaling"));
        }
        if (config.containsKey("clusters")) {
            d.put("clusters", config.get("clusters"));
        }
        return d;

View on GitHub (pinned to a22eb90246)