kestra-io/kestra · error · PebbleException

The 'subflow' function 'timeout' must be an ISO-8601 duratio

Error message

The 'subflow' function 'timeout' must be an ISO-8601 duration string (e.g. 'PT30S').

What it means

The `subflow()` function's `timeout` argument must be either null (use default), a `Duration` object, or a `String`. If it is any other type — a Number, Boolean, Map, or List — this `PebbleException` is thrown because no branch in `resolveTimeout` can handle it. Unlike error 220 (which is about an unparseable string), this is about a fundamentally wrong type.

Source

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

        // 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;
    }

    private Duration resolveTimeout(Object rawTimeout, PebbleTemplate self, int lineNumber) {
        Duration timeout;
        if (rawTimeout == null) {
            timeout = configuration.defaultTimeout();
        } else if (rawTimeout instanceof Duration d) {
            timeout = d;
        } else if (rawTimeout instanceof String s) {
            try {
                timeout = Duration.parse(s);
            } catch (DateTimeParseException e) {
                throw new PebbleException(e, "The 'subflow' function 'timeout' must be an ISO-8601 duration (e.g. 'PT30S'), got '" + s + "'.", lineNumber, self.getName());
            }
        } else {
            throw new PebbleException(null, "The 'subflow' function 'timeout' must be an ISO-8601 duration string (e.g. 'PT30S').", lineNumber, self.getName());
        }

        if (timeout.compareTo(configuration.maxTimeout()) > 0) {
            throw new PebbleException(null, "The 'subflow' function 'timeout' (" + timeout + ") exceeds the maximum allowed (" + configuration.maxTimeout() + ").", lineNumber, self.getName());
        }
        return timeout;
    }

    /**
     * The minimal, navigable result returned to the template, instead of the full {@link Execution}
     * object (which exposes internal state callers should not depend on).
     *
     * @param id the terminal execution id
     * @param state the terminal state name (e.g. {@code SUCCESS})
     * @param outputs the subflow's flow-level outputs, navigable as {@code subflow(...).outputs.xxx}
     * @param labels the execution labels as a {@code key -> value} map
     */
    public record Result(String id, String state, Map<String, Object> outputs, Map<String, String> labels) {

View on GitHub (pinned to 823fada927)

Solutions

  1. Pass the timeout as an ISO-8601 duration string: `timeout='PT30S'`.
  2. Omit `timeout` to use the default.
  3. If the value comes from a variable, coerce it to a properly formatted duration string before passing it.

Example fix

# before
values: "{{ subflow(namespace='company.team', id='my_flow', timeout=30) }}"
# after
values: "{{ subflow(namespace='company.team', id='my_flow', timeout='PT30S') }}"
Defensive patterns

Strategy: validation

Validate before calling

// Ensure timeout is null, a Duration, or a String before calling subflow()
public static String validateTimeoutArg(Object timeout) {
    if (timeout == null) return null; // use default
    if (timeout instanceof Duration || timeout instanceof String) {
        return timeout.toString();
    }
    throw new IllegalArgumentException("timeout must be a String or Duration, got: " + timeout.getClass());
}

Type guard

const isValidTimeoutType = (v: unknown): boolean => v === null || typeof v === 'string';

Prevention

When it happens

Trigger: Passing `timeout=30` (a numeric literal in the template) or `timeout=true` to `subflow()`. Passing a variable that resolves to a non-string, non-Duration object at render time.

Common situations: A template author writes `timeout=30` expecting it to mean 30 seconds, but Pebble evaluates `30` as an Integer. A flow input passes a numeric input value directly to `timeout`.

Understand the failure class

Related errors


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