{"record":{"id":"06775f478e458ad0","repo":"kestra-io/kestra","slug":"the-subflow-function-timeout-must-be-an-iso-86-06775f","errorCode":null,"errorMessage":"The 'subflow' function 'timeout' must be an ISO-8601 duration string (e.g. 'PT30S').","messagePattern":"The 'subflow' function 'timeout' must be an ISO-8601 duration string \\(e\\.g\\. 'PT30S'\\)\\.","errorType":"exception","errorClass":"PebbleException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/io/kestra/core/runners/pebble/functions/SubflowFunction.java","lineNumber":248,"sourceCode":"        // tag the execution as run by the subflow() function (cf. the Subflow task's system.from label)\n        labels.add(new Label(Label.FROM, Label.FromLabel.SUBFLOW.value));\n        return labels;\n    }\n\n    private Duration resolveTimeout(Object rawTimeout, PebbleTemplate self, int lineNumber) {\n        Duration timeout;\n        if (rawTimeout == null) {\n            timeout = configuration.defaultTimeout();\n        } else if (rawTimeout instanceof Duration d) {\n            timeout = d;\n        } else if (rawTimeout instanceof String s) {\n            try {\n                timeout = Duration.parse(s);\n            } catch (DateTimeParseException e) {\n                throw new PebbleException(e, \"The 'subflow' function 'timeout' must be an ISO-8601 duration (e.g. 'PT30S'), got '\" + s + \"'.\", lineNumber, self.getName());\n            }\n        } else {\n            throw new PebbleException(null, \"The 'subflow' function 'timeout' must be an ISO-8601 duration string (e.g. 'PT30S').\", lineNumber, self.getName());\n        }\n\n        if (timeout.compareTo(configuration.maxTimeout()) > 0) {\n            throw new PebbleException(null, \"The 'subflow' function 'timeout' (\" + timeout + \") exceeds the maximum allowed (\" + configuration.maxTimeout() + \").\", lineNumber, self.getName());\n        }\n        return timeout;\n    }\n\n    /**\n     * The minimal, navigable result returned to the template, instead of the full {@link Execution}\n     * object (which exposes internal state callers should not depend on).\n     *\n     * @param id the terminal execution id\n     * @param state the terminal state name (e.g. {@code SUCCESS})\n     * @param outputs the subflow's flow-level outputs, navigable as {@code subflow(...).outputs.xxx}\n     * @param labels the execution labels as a {@code key -> value} map\n     */\n    public record Result(String id, String state, Map<String, Object> outputs, Map<String, String> labels) {","sourceCodeStart":230,"sourceCodeEnd":266,"githubUrl":"https://github.com/kestra-io/kestra/blob/823fada9274c4f9c251ea0a516460a4f7d958032/core/src/main/java/io/kestra/core/runners/pebble/functions/SubflowFunction.java#L230-L266","documentation":"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.","triggerScenarios":"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.","commonSituations":"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`.","solutions":["Pass the timeout as an ISO-8601 duration string: `timeout='PT30S'`.","Omit `timeout` to use the default.","If the value comes from a variable, coerce it to a properly formatted duration string before passing it."],"exampleFix":"# before\nvalues: \"{{ subflow(namespace='company.team', id='my_flow', timeout=30) }}\"\n# after\nvalues: \"{{ subflow(namespace='company.team', id='my_flow', timeout='PT30S') }}\"","handlingStrategy":"validation","validationCode":"// Ensure timeout is null, a Duration, or a String before calling subflow()\npublic static String validateTimeoutArg(Object timeout) {\n    if (timeout == null) return null; // use default\n    if (timeout instanceof Duration || timeout instanceof String) {\n        return timeout.toString();\n    }\n    throw new IllegalArgumentException(\"timeout must be a String or Duration, got: \" + timeout.getClass());\n}","typeGuard":"const isValidTimeoutType = (v: unknown): boolean => v === null || typeof v === 'string';","tryCatchPattern":null,"preventionTips":["Never pass a numeric literal as timeout; always use a quoted ISO-8601 string.","If a numeric input must be used, convert it: `timeout='PT' ~ inputs.seconds ~ 'S'`."],"tags":["pebble","subflow","timeout","type-error","validation"],"backgroundTag":null,"analyzedSha":"823fada9274c4f9c251ea0a516460a4f7d958032","analyzedAt":"2026-08-14T06:15:17.947Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}