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
- Increase the retry count and/or interval in `configure retry = { count: 10, interval: 3000 }`.
- Verify the `retry until` expression is actually the success condition you intend (print `response` between retries).
- Check the target system/service logs to see why the expected condition never occurs.
- 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
- Size retry count/interval against measured worst-case backend latency.
- Log the polled value on each attempt so failures are diagnosable.
- Ensure the retry until expression tests the real completion condition.
- Add an explicit timeout budget so retries finish before outer suite timeouts.
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
- HTTP endpoint not available
- http method expression evaluated to null:
- retry interrupted
- expected status: , actual:
- multipart file requires '=' assignment:
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 skipView on GitHub (pinned to a22eb90246)