Kestra Flow Hardening Skill
Audit existing Kestra flows, surface resilience / idempotency / guardrail gaps as a
severity-ranked report, then apply the edits the user confirms.
This is the auditing counterpart to the sibling skills:
| Skill |
Owns |
kestra-flow |
Authoring — create / modify / debug flow YAML from intent |
kestra-flow-hardening |
Auditing — find gaps, recommend + apply guardrails |
kestra-ops |
Operating — validate / deploy / run via kestractl |
Hand off to kestra-flow for "build a new flow" and to kestra-ops for "validate / deploy this".
When to use
Trigger on requests to harden, audit, review, or make production-ready / resilient / idempotent:
- "Harden this flow / these flows for production."
- "Audit my flow — what's missing for reliability?"
- "Add retries, timeouts, and error handling to this flow."
- "Review this namespace's flows for resilience gaps."
Do not use this skill to author a brand-new flow from a description — that is kestra-flow.
Required inputs
- One or more flows, in priority order:
- Inline YAML pasted into the chat
- File path(s), glob, or directory (e.g.
./flows/, ./flows/*.yml)
- Live instance — flows fetched via
kestra-ops / kestractl (the user pairs the skills; this skill does not require a running instance)
- A namespace / directory batch
- Kestra version and edition (OSS / EE). Ask once if unknown; default to latest OSS. Used to gate version-/edition-specific recommendations.
Workflow
Step 1 — Fetch the flow schema (authoritative)
curl -s https://api.kestra.io/v1/plugins/schemas/flow
Read the raw JSON. The schema is the source of truth for which properties and types
exist in the target version. Never recommend a property absent from the schema — this is
what catches version traps such as retry.maxAttempt (pre-0.24) vs retry.maxAttempts.
Step 2 — Confirm scope and context
- Resolve the input form (paste / file / glob / dir / namespace).
- Confirm version + edition (default latest OSS if not given).
- For a directory or namespace, gather all target flows before reporting.
Step 3 — Calibrate by flow signals (proportionality)
Hardening must be proportional to what the flow does and where it runs. Read these
signals and tailor the audit — do not apply a fixed checklist to every flow:
- Triggers / scheduling present → concurrency, SLA, and overlap behavior become relevant; a manually-run flow needs them far less.
- Real side-effects (writes, external calls, cloud jobs, scripts) → resilience and idempotency findings matter; a pure-compute or log-only flow does not need them.
- Namespace as env hint —
prod.* / staging.* raises the bar; dev.* / sandbox.* / tutorial.* gets a lighter touch ("this looks like dev — hardening deferred unless you're promoting it").
- Controls already present → acknowledge them; never re-flag what's already there.
Never invent risk. If a flow is genuinely sound, returning "No critical or high findings;
this flow is reasonably hardened" is a correct, encouraged outcome. Do not manufacture
Low-severity nits to look busy.
Step 4 — Run the audit taxonomy
Evaluate each relevant dimension. For exact, copy-pasteable YAML per construct and the
"when to apply / when NOT to" guidance, load
references/hardening-patterns.md.
Resilience
- Retries on transient-failure-prone tasks (HTTP, JDBC, cloud APIs) — see idempotency tiers below.
- Timeouts on every runnable task (scripts, queries, cloud jobs — hang and cost control).
- Flow-level
retry where whole-execution replay makes sense.
Failure handling
errors (global) for alerting; local errors inside flowable tasks for targeted cleanup.
finally for resource teardown (containers, temp infra) — runs while execution is still RUNNING.
afterExecution for final-state-based notifications (SUCCESS / FAILED / WARNING) via runIf.
allowFailure / allowWarning on genuinely non-critical tasks only.
Concurrency & idempotency
concurrency limit + behavior (QUEUE / CANCEL / FAIL) for flows hitting shared / rate-limited systems.
- Idempotency guard —
system.correlationId + duplicate check (EE) or KV-based state guard (OSS).
- Trigger
allowConcurrent / Schedule overlap behavior.
Guardrails & contracts
checks (≥ 1.2) for pre-execution input validation.
sla — MAX_DURATION / EXECUTION_ASSERTION with breach labels + alerting.
- Input typing —
SELECT / ENUM / SECRET instead of loose STRING.
Hygiene
- Subflow extraction when a flow exceeds ~100 tasks or bloats the execution context.
store: true / stores for large outputs instead of carrying data in the execution context.
- Descriptions, labels, naming conventions.
- No hardcoded secrets / credentials (reuse
kestra-flow's rule — see Shared rules below).
Platform-fit & maintainability (advisory — not hardening, never a blocker)
These are recommendations, not risks. They never carry a severity and never block; they
surface in a separate Advisory section (see report format). Use them to nudge users
toward better-maintained flows, not to gate anything.
- Long inline scripts → Namespace Files. A
Script task with a large inline script: /
commands: block (roughly > 15–20 lines, or containing real program logic) is harder to
read, test, and version. Recommend moving the code into a Namespace File and calling it
from a Commands task (namespaceFiles: {enabled: true, include: [...]}), or reading it via
read('path') in a Script task. Kestra's own guidance: Commands for production
workloads, Script for quick iteration.
- Scripts duplicating plugin functionality → native plugin. When a script reimplements
something a Kestra plugin already does, recommend the native task — less custom code to
maintain. Pattern-match common usage to plugin families and verify the task exists in the
fetched schema before naming it:
curl / wget / requests → io.kestra.plugin.core.http.Request / Download
psql / mysql / sqlite3 → the matching JDBC plugin (io.kestra.plugin.jdbc.*)
aws / gcloud / az CLI → the cloud plugins (io.kestra.plugin.aws|gcp|azure.*)
dbt → io.kestra.plugin.dbt.*; git clone → io.kestra.plugin.git.Clone
- If Namespace Files are available in context, assess how much of the scripted logic could
be replaced by native tasks and quantify it ("~X of N steps map to native plugin tasks").
Step 5 — Classify each finding by severity
| Severity |
Definition |
Example |
| Critical |
Will cause data corruption, duplicate side-effects, or silent loss in production |
Retry / allowFailure on a non-idempotent write; no concurrency limit on non-atomic state updates; hardcoded secret |
| High |
Will cause outages / hangs / cost-blowouts or undetected failures |
No timeout on a cloud / script task; no errors alerting; no SLA on a business-critical schedule |
| Medium |
Degraded resilience / recoverability |
Missing transient retries on HTTP / JDBC; no finally teardown leaking resources; loose input typing |
| Low |
Hygiene / maintainability |
Missing descriptions / labels; naming; large-output store unset |
Platform-fit recommendations (long scripts → Namespace Files; scripts → native plugins) are
not assigned a severity. They are reported separately as Advisory and never block.
Step 6 — Apply the idempotency judgment (before recommending retries)
A retry is good advice for a flaky read and catastrophic for a non-idempotent write
(double-charge, duplicate insert). Classify every candidate task before recommending:
| Tier |
How detected |
Retry recommendation |
| Safe |
Read-only by type (HTTP GET, SELECT, fetch / list, Log) |
Recommend retries freely |
| Conditionally safe |
Write with a natural idempotency key, or transactional / upsert task |
Recommend retries with the precondition stated |
| Unsafe / unknown |
Opaque scripts, POST / PUT to unknown endpoints, non-transactional writes |
Do NOT recommend a blind retry. Flag the idempotency question in the report with both branches: dedup guard (correlationId / KV) vs. retry-if-safe. Let the user decide at confirm-time. |
Step 7 — Emit the report
Format (findings numbered globally so the user can select by number):
## Hardening audit: <flow id> (<namespace>, v<X.Y>, OSS/EE)
### Critical
1. **<title>** — `tasks.<id>`
Risk: <what breaks in production if unfixed>
Caveat: <e.g. only safe if this endpoint dedupes on a key>
Proposed: <the edit, or both branches for unknown-safety tasks>
### High
2. ...
### Medium
### Low
### Advisory (platform-fit) — not hardening, optional
- **Long inline script** — `tasks.transform`
Move the ~40-line script into a Namespace File and call it from a `Commands` task
(versioned, testable, editable in the Code Editor).
- **Script duplicates a plugin** — `tasks.fetch`
This `curl` shell task can be `io.kestra.plugin.core.http.Request` (verified in schema).
---
Summary: N Critical · N High · N Medium · N Low · N Advisory
Reply with the numbers to apply (e.g. `1,4,7`), `all`, or `none`.
- Group by severity; each finding states risk + caveat + proposed fix + severity.
- Diffs are deferred to confirm-time — keep the report scannable.
- Mark structural edits (idempotency-guard insertion, flow-level retry behavior) clearly, since they add tasks rather than tweak one line.
- Batch: emit one consolidated report ranked by severity across all flows, with a per-flow summary table; then edit flow-by-flow on confirm.
- EE-only findings (e.g. correlationId guard): always label as EE, give the OSS fallback (KV guard), and frame the EE path as a value-add.
Step 8 — Apply confirmed edits
On apply 1,4,7 / all:
- Surgical, structure-preserving edits. Touch only the relevant tasks / blocks. Preserve root
id / namespace, task ordering, and comments. Never restructure unrelated parts.
- File input → edit the file in place. Pasted YAML → return the modified YAML. Batch → edit each file.
- Re-validate every edit against the fetched schema (properties and types must exist).
- The initial number-selection is informed consent (the report already stated each risk / caveat) — do not re-prompt per finding, but show the diff as you apply it.
- Suggest
kestractl flow validate (via kestra-ops) as an optional final gate; do not require a live instance.
Shared rules (inherited from kestra-flow)
Do not restate — reuse the kestra-flow skill's rules so they don't drift:
- Schema compliance — only schema-defined task types and properties.
- No hardcoded secrets / credentials — use
inputs of type SECRET or {{ secret('...') }}.
- Quoting — prefer double quotes; single quotes inside when needed.
- Structural preservation — touch only the relevant part; preserve
id / namespace.
Example prompts
- "Harden this flow for production." (inline paste)
- "Audit
./flows/ingest.yml and add retries and timeouts where safe."
- "Review all flows in
./flows/ for resilience gaps and rank them worst-first."
- "Make this scheduled flow idempotent — it sometimes runs twice." (idempotency tier judgment)
- "Add alerting and an SLA to this business-critical flow."
1---2name: kestra-flow-hardening3description: Audit one or more existing Kestra flows and add production-hardening controls — retries, timeouts, concurrency limits, error/finally/afterExecution handlers, SLAs, checks, and idempotency guards. Produces a severity-ranked findings report, then applies confirmed edits. Use when users ask to harden, audit, review, or make a flow more production-ready, resilient, or idempotent — not for authoring new flows (use kestra-flow).4---56# Kestra Flow Hardening Skill78Audit existing Kestra flows, surface resilience / idempotency / guardrail gaps as a9severity-ranked report, then apply the edits the user confirms.1011This is the **auditing** counterpart to the sibling skills:1213| Skill | Owns |14|-------|------|15| `kestra-flow` | **Authoring** — create / modify / debug flow YAML from intent |16| **`kestra-flow-hardening`** | **Auditing** — find gaps, recommend + apply guardrails |17| `kestra-ops` | **Operating** — validate / deploy / run via `kestractl` |1819Hand off to `kestra-flow` for "build a new flow" and to `kestra-ops` for "validate / deploy this".2021## When to use2223Trigger on requests to **harden, audit, review, or make production-ready / resilient / idempotent**:24- "Harden this flow / these flows for production."25- "Audit my flow — what's missing for reliability?"26- "Add retries, timeouts, and error handling to this flow."27- "Review this namespace's flows for resilience gaps."2829Do **not** use this skill to author a brand-new flow from a description — that is `kestra-flow`.3031## Required inputs3233- One or more flows, in priority order:34 1. Inline YAML pasted into the chat35 2. File path(s), glob, or directory (e.g. `./flows/`, `./flows/*.yml`)36 3. Live instance — flows fetched via `kestra-ops` / `kestractl` (the user pairs the skills; this skill does not require a running instance)37 4. A namespace / directory batch38- **Kestra version and edition (OSS / EE).** Ask once if unknown; default to **latest OSS**. Used to gate version-/edition-specific recommendations.3940## Workflow4142### Step 1 — Fetch the flow schema (authoritative)4344```bash45curl -s https://api.kestra.io/v1/plugins/schemas/flow46```4748Read the raw JSON. The schema is the **source of truth** for which properties and types49exist in the target version. Never recommend a property absent from the schema — this is50what catches version traps such as `retry.maxAttempt` (pre-0.24) vs `retry.maxAttempts`.5152### Step 2 — Confirm scope and context5354- Resolve the input form (paste / file / glob / dir / namespace).55- Confirm version + edition (default latest OSS if not given).56- For a directory or namespace, gather **all** target flows before reporting.5758### Step 3 — Calibrate by flow signals (proportionality)5960Hardening must be proportional to what the flow does and where it runs. Read these61signals and tailor the audit — do **not** apply a fixed checklist to every flow:6263- **Triggers / scheduling** present → concurrency, SLA, and overlap behavior become relevant; a manually-run flow needs them far less.64- **Real side-effects** (writes, external calls, cloud jobs, scripts) → resilience and idempotency findings matter; a pure-compute or log-only flow does not need them.65- **Namespace as env hint** — `prod.*` / `staging.*` raises the bar; `dev.*` / `sandbox.*` / `tutorial.*` gets a lighter touch ("this looks like dev — hardening deferred unless you're promoting it").66- **Controls already present** → acknowledge them; never re-flag what's already there.6768**Never invent risk.** If a flow is genuinely sound, returning *"No critical or high findings;69this flow is reasonably hardened"* is a correct, encouraged outcome. Do not manufacture70Low-severity nits to look busy.7172### Step 4 — Run the audit taxonomy7374Evaluate each relevant dimension. For exact, copy-pasteable YAML per construct and the75"when to apply / when NOT to" guidance, load76[`references/hardening-patterns.md`](references/hardening-patterns.md).7778**Resilience**79- Retries on transient-failure-prone tasks (HTTP, JDBC, cloud APIs) — see idempotency tiers below.80- Timeouts on every runnable task (scripts, queries, cloud jobs — hang and cost control).81- Flow-level `retry` where whole-execution replay makes sense.8283**Failure handling**84- `errors` (global) for alerting; local `errors` inside flowable tasks for targeted cleanup.85- `finally` for resource teardown (containers, temp infra) — runs while execution is still RUNNING.86- `afterExecution` for final-state-based notifications (SUCCESS / FAILED / WARNING) via `runIf`.87- `allowFailure` / `allowWarning` on genuinely non-critical tasks only.8889**Concurrency & idempotency**90- `concurrency` limit + `behavior` (QUEUE / CANCEL / FAIL) for flows hitting shared / rate-limited systems.91- Idempotency guard — `system.correlationId` + duplicate check (EE) or KV-based state guard (OSS).92- Trigger `allowConcurrent` / Schedule overlap behavior.9394**Guardrails & contracts**95- `checks` (≥ 1.2) for pre-execution input validation.96- `sla` — `MAX_DURATION` / `EXECUTION_ASSERTION` with breach labels + alerting.97- Input typing — `SELECT` / `ENUM` / `SECRET` instead of loose `STRING`.9899**Hygiene**100- Subflow extraction when a flow exceeds ~100 tasks or bloats the execution context.101- `store: true` / `stores` for large outputs instead of carrying data in the execution context.102- Descriptions, labels, naming conventions.103- No hardcoded secrets / credentials (reuse `kestra-flow`'s rule — see Shared rules below).104105**Platform-fit & maintainability (advisory — not hardening, never a blocker)**106These are recommendations, not risks. They never carry a severity and never block; they107surface in a separate **Advisory** section (see report format). Use them to nudge users108toward better-maintained flows, not to gate anything.109- **Long inline scripts → Namespace Files.** A `Script` task with a large inline `script:` /110 `commands:` block (roughly > 15–20 lines, or containing real program logic) is harder to111 read, test, and version. Recommend moving the code into a **Namespace File** and calling it112 from a `Commands` task (`namespaceFiles: {enabled: true, include: [...]}`), or reading it via113 `read('path')` in a `Script` task. Kestra's own guidance: `Commands` for production114 workloads, `Script` for quick iteration.115- **Scripts duplicating plugin functionality → native plugin.** When a script reimplements116 something a Kestra plugin already does, recommend the native task — less custom code to117 maintain. Pattern-match common usage to plugin families and **verify the task exists in the118 fetched schema before naming it**:119 - `curl` / `wget` / `requests` → `io.kestra.plugin.core.http.Request` / `Download`120 - `psql` / `mysql` / `sqlite3` → the matching JDBC plugin (`io.kestra.plugin.jdbc.*`)121 - `aws` / `gcloud` / `az` CLI → the cloud plugins (`io.kestra.plugin.aws|gcp|azure.*`)122 - `dbt` → `io.kestra.plugin.dbt.*`; `git` clone → `io.kestra.plugin.git.Clone`123- **If Namespace Files are available in context**, assess how much of the scripted logic could124 be replaced by native tasks and quantify it ("~X of N steps map to native plugin tasks").125126### Step 5 — Classify each finding by severity127128| Severity | Definition | Example |129|----------|------------|---------|130| **Critical** | Will cause data corruption, duplicate side-effects, or silent loss in production | Retry / `allowFailure` on a non-idempotent write; no concurrency limit on non-atomic state updates; hardcoded secret |131| **High** | Will cause outages / hangs / cost-blowouts or undetected failures | No timeout on a cloud / script task; no `errors` alerting; no SLA on a business-critical schedule |132| **Medium** | Degraded resilience / recoverability | Missing transient retries on HTTP / JDBC; no `finally` teardown leaking resources; loose input typing |133| **Low** | Hygiene / maintainability | Missing descriptions / labels; naming; large-output `store` unset |134135Platform-fit recommendations (long scripts → Namespace Files; scripts → native plugins) are136**not** assigned a severity. They are reported separately as **Advisory** and never block.137138### Step 6 — Apply the idempotency judgment (before recommending retries)139140A retry is good advice for a flaky read and **catastrophic** for a non-idempotent write141(double-charge, duplicate insert). Classify every candidate task before recommending:142143| Tier | How detected | Retry recommendation |144|------|--------------|----------------------|145| **Safe** | Read-only by type (HTTP GET, SELECT, fetch / list, Log) | Recommend retries freely |146| **Conditionally safe** | Write with a natural idempotency key, or transactional / upsert task | Recommend retries **with the precondition stated** |147| **Unsafe / unknown** | Opaque scripts, POST / PUT to unknown endpoints, non-transactional writes | **Do NOT recommend a blind retry.** Flag the idempotency question in the report with **both branches**: dedup guard (correlationId / KV) vs. retry-if-safe. Let the user decide at confirm-time. |148149### Step 7 — Emit the report150151Format (findings numbered **globally** so the user can select by number):152153```154## Hardening audit: <flow id> (<namespace>, v<X.Y>, OSS/EE)155156### Critical1571. **<title>** — `tasks.<id>`158 Risk: <what breaks in production if unfixed>159 Caveat: <e.g. only safe if this endpoint dedupes on a key>160 Proposed: <the edit, or both branches for unknown-safety tasks>161162### High1632. ...164165### Medium166### Low167168### Advisory (platform-fit) — not hardening, optional169- **Long inline script** — `tasks.transform`170 Move the ~40-line script into a Namespace File and call it from a `Commands` task171 (versioned, testable, editable in the Code Editor).172- **Script duplicates a plugin** — `tasks.fetch`173 This `curl` shell task can be `io.kestra.plugin.core.http.Request` (verified in schema).174175---176Summary: N Critical · N High · N Medium · N Low · N Advisory177Reply with the numbers to apply (e.g. `1,4,7`), `all`, or `none`.178```179180- Group by severity; each finding states **risk + caveat + proposed fix + severity**.181- **Diffs are deferred to confirm-time** — keep the report scannable.182- Mark **structural edits** (idempotency-guard insertion, flow-level retry behavior) clearly, since they add tasks rather than tweak one line.183- **Batch**: emit one consolidated report ranked by severity **across all flows**, with a per-flow summary table; then edit flow-by-flow on confirm.184- **EE-only findings** (e.g. correlationId guard): always label as EE, give the OSS fallback (KV guard), and frame the EE path as a value-add.185186### Step 8 — Apply confirmed edits187188On `apply 1,4,7` / `all`:189- **Surgical, structure-preserving edits.** Touch only the relevant tasks / blocks. Preserve root `id` / `namespace`, task ordering, and comments. Never restructure unrelated parts.190- **File input** → edit the file in place. **Pasted YAML** → return the modified YAML. **Batch** → edit each file.191- **Re-validate** every edit against the fetched schema (properties and types must exist).192- The initial number-selection **is** informed consent (the report already stated each risk / caveat) — do not re-prompt per finding, but show the diff as you apply it.193- Suggest `kestractl flow validate` (via `kestra-ops`) as an optional final gate; do not require a live instance.194195## Shared rules (inherited from `kestra-flow`)196197Do not restate — reuse the `kestra-flow` skill's rules so they don't drift:198- Schema compliance — only schema-defined task types and properties.199- No hardcoded secrets / credentials — use `inputs` of type `SECRET` or `{{ secret('...') }}`.200- Quoting — prefer double quotes; single quotes inside when needed.201- Structural preservation — touch only the relevant part; preserve `id` / `namespace`.202203## Example prompts204205- "Harden this flow for production." *(inline paste)*206- "Audit `./flows/ingest.yml` and add retries and timeouts where safe."207- "Review all flows in `./flows/` for resilience gaps and rank them worst-first."208- "Make this scheduled flow idempotent — it sometimes runs twice." *(idempotency tier judgment)*209- "Add alerting and an SLA to this business-critical flow."