kestra-io/kestra · error · IllegalVariableEvaluationException

Too many rendering attempts

Error message

Too many rendering attempts

What it means

Thrown by VariableRenderer when recursive rendering exceeds MAX_RENDERING_AMOUNT (100 iterations). The renderer re-evaluates the template output after each pass, stopping when the result stops changing. If every pass produces new Pebble syntax (the output keeps mutating), the loop never converges and this guard fires after 101 render cycles.

Source

Thrown at core/src/main/java/io/kestra/core/runners/VariableRenderer.java:172

        return result;
    }

    private static String replaceRawTags(Matcher rawMatcher, Map<String, String> replacers) {
        return rawMatcher.replaceAll(matchResult ->
        {
            var uuid = UUID.randomUUID().toString();
            replacers.put(uuid, matchResult.group(1));
            return uuid;
        });
    }

    public Object renderRecursively(Object inline, Map<String, Object> variables, boolean stringify) throws IllegalVariableEvaluationException {
        return this.renderRecursively(0, inline, variables, stringify);
    }

    private Object renderRecursively(int renderingCount, Object inline, Map<String, Object> variables, boolean stringify) throws IllegalVariableEvaluationException {
        if (renderingCount > MAX_RENDERING_AMOUNT) {
            throw new IllegalVariableEvaluationException("Too many rendering attempts");
        }

        Object result = this.renderOnce(inline, variables, stringify);
        if (result == null || Objects.equals(result, inline)) {
            return result;
        }

        return renderRecursively(++renderingCount, result, variables, stringify);
    }

    public Map<String, Object> render(Map<String, Object> in, Map<String, Object> variables) throws IllegalVariableEvaluationException {
        return this.render(in, variables, this.variableConfiguration.getRecursiveRendering());
    }

    public Map<String, Object> render(Map<String, Object> in, Map<String, Object> variables, boolean recursive) throws IllegalVariableEvaluationException {
        Map<String, Object> map = new LinkedHashMap<>();

        for (Map.Entry<String, Object> r : in.entrySet()) {

View on GitHub (pinned to 823fada927)

Solutions

  1. Identify the self-referential or infinitely-expanding variable by examining the flow's outputs at each render pass; trace which variable's value keeps producing new template syntax.
  2. Break the recursion cycle: ensure the rendered output of any variable does not itself contain unresolvable '{{ }}' or '{% %}' that maps back to the same variable chain.
  3. Wrap the dynamic content in '{% raw %}...{% endraw %}' blocks so the renderer does not re-evaluate it on subsequent passes.
  4. Flatten deeply chained variable lookups into a single non-recursive expression, or compute the value in a script task instead of in the template engine.

Example fix

# before (self-referential — never converges)
vars:
  greeting: "Hello {{ vars.greeting }}"

# after (no recursion)
vars:
  name: "World"
  greeting: "Hello {{ vars.name }}"
Defensive patterns

Strategy: try-catch

Validate before calling

// In Java, before calling render, scan the string for self-referential variable patterns
boolean isLikelySelfReferential(String template, String varName) {
    return template != null && varName != null
        && template.contains("{{ vars." + varName)
        && template.contains(varName);
}

Try / catch

try {
    String rendered = variableRenderer.render(template, variables);
} catch (IllegalVariableEvaluationException e) {
    if (e.getMessage().contains("Too many rendering attempts")) {
        // log the template and variables for diagnosis, break recursion manually
        log.warn("Recursive rendering loop detected in template: {}", template);
    }
    throw e;
}

Prevention

When it happens

Trigger: A flow variable whose rendered value itself contains '{{ }}' or '{% %}' that re-expands into more template syntax on the next pass, creating an infinite expansion chain. Common trigger: a variable like '{{ vars.selfreferential }}' where the resolved value embeds another reference to itself, or a subflow output that loops template variables back in. Also triggered when one variable resolves to a string containing another variable name that resolves to yet another, 100+ levels deep.

Common situations: Dynamic variable construction where outputs feed back as inputs (e.g., building JSON that itself contains template expressions). Subflow chaining with deeply nested outputs. Using a variable to generate the key of another variable recursively. Templating that produces '{{ }}' literally inside the rendered result.

Related errors


AI-assisted analysis of kestra-io/kestra@823fada927 (2026-08-14). Data as JSON: /api/errors/516062c4574834d0. Report an issue: GitHub.