karatelabs/karate · error · RuntimeException

retry failed after attempts:

Error message

retry failed after  attempts: 

What it means

Karate's `retry until` mechanism re-executes a step (typically an HTTP call) until a condition is met. When the condition is still not satisfied after `maxRetries` attempts (configured via `configure retry = { count: N, interval: M }`), StepExecutor throws this RuntimeException naming the retry count and the `retry until` expression that kept failing.

Solutions

  1. Increase the retry count and/or interval in `configure retry = { count: 10, interval: 3000 }`.
  2. Verify the `retry until` expression is actually the success condition you intend (print `response` between retries).
  3. Check the target system/service logs to see why the expected condition never occurs.
  4. If the operation is legitimately long-running, replace polling with a webhook/callback or an explicit status endpoint called in a loop with fail-fast logging.

Example fix

// before
configure retry = { count: 3, interval: 1000 }
And retry until responseStatus == 200
// after
configure retry = { count: 10, interval: 5000 }
And retry until responseStatus == 200 && response.status == 'DONE'
Defensive patterns

Strategy: retry

Validate before calling

// Before running, sanity-check retry config and condition
* def retryCfg = { count: 10, interval: 5000 }
* configure retry = retryCfg
* assert retryCfg.count > 0 && retryCfg.interval > 0

Try / catch

// Runner-level
try {
    karate.run(feature);
} catch (RuntimeException e) {
    if (e.getMessage().startsWith("retry failed after")) {
        // inspect the target system; treat as environment/condition failure
    }
    throw e;
}

Prevention

When it happens

Trigger: A step annotated with `retry until <expression>` was executed and the expression remained false for `maxRetries` consecutive evaluations, with the configured retry interval slept between each attempt.

Common situations: Waiting for an async job to finish but the backend is genuinely slow or stuck; wrong `retry until` expression that never becomes true (e.g. comparing `responseStatus == 200` when the endpoint always returns 202); retry count configured too low for the operation's latency; environment slowness in CI.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


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

Appendix: source

Thrown at karate-core/src/main/java/io/karatelabs/core/StepExecutor.java:2334

        } else if (body instanceof Map || body instanceof List) {
            responseType = "json";
        } else if (body instanceof org.w3c.dom.Node) {
            responseType = "xml";
        } else {
            responseType = "string";
        }
        runtime.setHiddenVariable("responseType", responseType);
    }

    private HttpResponse executeMethodWithRetry(String method, String retryUntil, KarateConfig config) {
        int maxRetries = config.getRetryCount();
        int sleepInterval = config.getRetryInterval();
        int retryCount = 0;
        Suite suite = getSuite();

        while (true) {
            if (retryCount == maxRetries) {
                throw new RuntimeException("retry failed after " + maxRetries + " attempts: " + retryUntil);
            }
            if (retryCount > 0) {
                try {
                    logger.debug("sleeping {} ms before retry #{}", sleepInterval, retryCount);
                    Thread.sleep(sleepInterval);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException("retry interrupted", e);
                }
            }

            // Make a copy of the request builder to preserve state for retry
            HttpRequestBuilder httpCopy = http().copy();

            // Build request for HTTP_ENTER event (method already set in doMethod)
            HttpRequest request = http().build();

            // Fire HTTP_ENTER event - listener can return false to skip

View on GitHub (pinned to a22eb90246)