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
- Use Pebble map syntax with curly braces: subflow(..., labels={'key': 'value'}).
- Ensure the labels variable resolves to a Map type, not a JSON string.
- 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
- Use Pebble map syntax (curly braces) for labels, never JSON strings.
- Verify label variables resolve to Map types before passing them.
- Double-check during code review that labels arguments use {key: value} syntax.
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
- The 'subflow' function cannot set the system label '%s'; sys
- The 'subflow' function 'timeout' must be an ISO-8601 duratio
- 'values' filter can only be applied to Map. Actual type was:
- The 'render' function expects an optional argument 'recursiv
- The 'subflow' function can only be used at flow-input render
AI-assisted analysis of kestra-io/kestra@823fada927 (2026-08-14).
Data as JSON: /api/errors/abc3888d17ba9110.
Report an issue: GitHub.