kestra-io/kestra · error · PebbleException

The 'subflow' function 'labels' must be a map of string keys

Error message

The 'subflow' function 'labels' must be a map of string keys to values.

What it means

The subflow() function accepts an optional 'labels' argument that must be a Pebble map (key-value pairs). The buildLabels() helper checks that the resolved value is an instance of Map. If a non-map value is passed (a string, a list, a number), the function throws before attempting any label construction.

Source

Thrown at core/src/main/java/io/kestra/core/runners/pebble/functions/SubflowFunction.java:214

            }

            return Result.of(terminated);
        } finally {
            int current = DEPTH.get() - 1;
            if (current <= 0) {
                DEPTH.remove();
            } else {
                DEPTH.set(current);
            }
        }
    }

    @SuppressWarnings("unchecked")
    private List<Label> buildLabels(Object rawLabels, PebbleTemplate self, int lineNumber) {
        List<Label> labels = new ArrayList<>();
        if (rawLabels != null) {
            if (!(rawLabels instanceof Map)) {
                throw new PebbleException(null, "The 'subflow' function 'labels' must be a map of string keys to values.", lineNumber, self.getName());
            }
            ((Map<String, Object>) rawLabels).forEach((key, value) ->
            {
                if (value != null) {
                    // system labels are reserved for Kestra; only system.correlationId may be propagated by the caller
                    if (key.startsWith(Label.SYSTEM_PREFIX) && !key.equals(Label.CORRELATION_ID)) {
                        throw new PebbleException(
                            null, "The 'subflow' function cannot set the system label '" + key + "'; system labels are reserved (except '" + Label.CORRELATION_ID + "').", lineNumber,
                            self.getName()
                        );
                    }
                    labels.add(new Label(key, String.valueOf(value)));
                }
            });
        }
        // tag the execution as run by the subflow() function (cf. the Subflow task's system.from label)
        labels.add(new Label(Label.FROM, Label.FromLabel.SUBFLOW.value));
        return labels;

View on GitHub (pinned to 823fada927)

Solutions

  1. Use Pebble map syntax with curly braces: subflow(..., labels={'key': 'value'}).
  2. Ensure the labels variable resolves to a Map type, not a JSON string.
  3. If building labels from a variable, verify it is a map at render time.

Example fix

# before — labels as a JSON string or list
{{ subflow(namespace='ns', id='child', labels='{"env":"prod"}') }}
{{ subflow(namespace='ns', id='child', labels=['env','prod']) }}

# after — Pebble map syntax
{{ subflow(namespace='ns', id='child', labels={'env': 'prod'}) }}
Defensive patterns

Strategy: type-guard

Validate before calling

# Always pass labels as a Pebble map (curly braces), not a JSON string or list.
# Correct: {{ subflow(namespace='ns', id='child', labels={'env': 'prod'}) }}
# If using a variable, ensure it resolves to a Map:
{% set my_labels = {'env': 'prod', 'team': 'data'} %}
{{ subflow(namespace='ns', id='child', labels=my_labels) }}

Prevention

When it happens

Trigger: Passing labels as a JSON string: subflow(..., labels='{"key":"value"}'). Passing a list: subflow(..., labels=['key','value']). Passing a single string or number. Passing an expression that resolves to a non-map type.

Common situations: Confusing Pebble map syntax (curly braces) with JSON string syntax. A variable that was expected to be a map but resolves to a string. Copy-pasting labels from a JSON example without converting to Pebble map syntax.

Related errors


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