New to Kestra?
Use blueprints to kickstart your first workflows.
Validate live Kestra flows against a candidate Kestra version and quarantine triggers for new invalid flows before upgrade.
A Kestra upgrade can leave a deployed flow accepted by the current server but rejected when its next trigger runs. This gate makes the candidate validation result explicit before the rollout and disables only triggers on flows that become invalid on the candidate.
| Guard | What it stops |
|---|---|
| Current versus candidate validation | Existing problems are not misreported as upgrade regressions. |
| Candidate constraint and replacement detection | A missing plugin type or invalid constraint is classified as INVALID. |
Pause after the report |
Trigger writes require a human decision and a fresh state read. |
| KV rollback records | Every flow whose triggers are disabled has the trigger ids that were enabled recorded. The record holds no credential. |
| Post-write trigger read | The run fails if any enabled trigger is still enabled after Toggle. |
| Flow-level queue and candidate revalidation | Two executions never write together and a flow fixed during the Pause is revalidated and skipped. |
inventory (io.kestra.plugin.kestra.flows.List) reads the live namespace and export (io.kestra.plugin.kestra.flows.Export) stores a complete source snapshot in Kestra internal storage.scan (io.kestra.plugin.core.flow.Loop) sends each flow representation to the current and candidate /flows/validate endpoints with io.kestra.plugin.core.http.Request.facts and classify (io.kestra.plugin.core.output.OutputValues) subtract current deprecation paths and warnings from candidate results. A candidate constraint that differs from the current constraint or a replacement notice, is INVALID; a new deprecation is NEW_DEPRECATION; a new warning is NEW_WARNING; all else is UNCHANGED.INVALID result is recorded with io.kestra.plugin.core.kv.Set. The dry-run report is emitted as one table row per flow in the execution log.auto_remediate is true and dry_run is false, approval (io.kestra.plugin.core.flow.Pause) holds the finding keys. After the approval each flow is handled on its own: recheck_source and recheck_candidate read its source again and validate it on the candidate and a flow that no longer has a candidate constraint or replacement notice is skipped. prewrite_inventory reads the runtime trigger state and the write goes ahead only when a trigger is enabled both at runtime and in the flow source. save_original stores the enabled trigger ids in KV, io.kestra.plugin.kestra.triggers.Toggle disables the flow's triggers and after a short wait verify_inventory fails the run if an enabled trigger remains. The trigger reads use core.http.Request because the Kestra flow inventory task exposes source triggers but not the persisted runtime flag. The flow runs one execution at a time (concurrency limit 1 with QUEUE), so two runs never write together.A per-flow classification table, candidate-only deprecation and warning deltas and auditable KV records for every trigger quarantined before an upgrade.
Platform teams that run Kestra in production and need a pre-upgrade decision with a bounded, reversible quarantine action.
The same instance that owns the live flows can export them, validate them through both Kestra APIs, pause for an operator and record every trigger change in its execution history. The plugin tasks keep selection and writes tied to Kestra identifiers instead of a separately maintained inventory script.
Kestra 2.0.6 ran the gate. A Kestra 2.0.5 server held the live flows and a second Kestra 2.0.6 server was the candidate, all on disposable containers. The fixture holds a healthy control, a flow that uses a task type the candidate no longer ships, a flow whose dbt Cloud task uses two properties the candidate newly deprecates and an invalid flow whose only trigger is already disabled in the flow source. Trigger state was read from the current server's own trigger API after every case, separately from the flow's reads.
The approved run disabled exactly one trigger and left the other three flows alone. The password is a SECRET input; it was absent from every execution, child execution, log and KV record the run produced. Each flow is revalidated on the candidate right before its write, so a flow fixed during the Pause keeps its trigger.
| Case | Execution | Result |
|---|---|---|
| Clean namespace | cB18atBsf4qRUl9VnQIQw |
SUCCESS. One UNCHANGED row, no record, no write |
| Dry run | 4ljJuhitgm4a4SyzjLD3V9 |
SUCCESS. INVALID x2, NEW_DEPRECATION x1, UNCHANGED x1; no trigger changed |
| Declined approval | 2Iz2XucH7ZnIVt3CC0oxHH |
SUCCESS. No Toggle, every trigger state unchanged |
| Approved quarantine | nAhgkak7WuANT0fwtDKWC |
SUCCESS. Only the candidate-invalid flow with an enabled trigger was disabled and verified; healthy, deprecated and source-disabled flows untouched; one rollback record and no password stored anywhere |
| Repeat | QCZGfJPq55gY4LMmFBx1R |
SUCCESS. Trigger already disabled, no second Toggle |
| Trigger re-enabled during the settle window | 64pZSRoRnAvWvh07uKJbSY |
FAILED as intended. Toggle reported success, the read-back found the trigger enabled |
| Flow fixed during the Pause | 3co5uNCy8OCKlI3P5io7Av |
SUCCESS. Revalidation found no candidate problem, trigger left enabled |
| Trigger disabled during the Pause | 4xkhqYQHfJGJEUfcmFdhIH |
SUCCESS. Skipped as stale, no Toggle |
| Current server stopped during the Pause | 2jklmoQFyC28nvZjLEYOpW |
FAILED as intended at the source re-read, no Toggle |
| Fresh servers: clean namespace | i8hz7TtUhO2OncTX94N7K |
SUCCESS |
| Fresh servers: dry run | 1RZsu0Ta8hT4X1PMFfKvJG |
SUCCESS. Same four classifications |
| Fresh servers: declined | 1rf1Et4IYkUAmo9KXti94O |
SUCCESS |
| Fresh servers: approved quarantine | 6crDNERlpCvhj9JIzcbqfp |
SUCCESS. Same single trigger disabled |
| Fresh servers: repeat | 7XD1soLkXszJurcPlKNXWM |
SUCCESS |
| Fresh servers: trigger re-enabled during settle | 1NlRTiMizP89pAAF73L92C |
FAILED as intended |
| Fresh servers: flow fixed during the Pause | 5F0lTzXsj9KyUUHsnfzWmB |
SUCCESS |
| Fresh servers: trigger disabled during the Pause | 3x2mtuFQE40bjVwufzkI1d |
SUCCESS |
| Fresh servers: current server stopped | AWRrWWAOig8i0oD1g0okp |
FAILED as intended |
The classifier ran on synthetic validation results, through the same classify task copied unchanged into a probe flow, to cover boundaries a stored flow cannot reach (the current server refuses to store a flow that is invalid for it):
| Classifier check | Execution | Class |
|---|---|---|
| candidate adds first constraint | 7dI3X5Qxt9iP5sOiUtp5Qx |
INVALID |
| candidate changes existing constraint | 7YggGbhBakkzjNVs5yt8Kj |
INVALID |
| same constraint on both | 2A1eru7q5SQY1UnX1edSjY |
UNCHANGED |
| candidate fixes constraint | 3eVbAAcmoBLuWLkmsdYGP8 |
UNCHANGED |
| replacement notice | 1sYo4Syv6TGPiythYBVNj1 |
INVALID |
| new deprecation only | 4mEt18PwbUgEKsABYwABLn |
NEW_DEPRECATION |
| deprecation already on current | 4FeV8bg1Dj9v0tnuVnIlps |
UNCHANGED |
| new warning only | 6RA8mllDObFY31wawF12mJ |
NEW_WARNING |
Kestra OSS 2.0.6 with the Kestra and core plugins, an API account that can list and export flows, validate flows, set KV values and toggle triggers and a second Kestra URL running the candidate image. The worker must be able to reach both URLs.
KESTRA_PASSWORD: password for the Kestra API account.SLACK_WEBHOOK_URL: Slack-compatible incoming webhook for the errors handler.DBT_CLOUD_TOKEN: only needed by fixture flows that use dbt Cloud.| Input | Purpose | Safe default |
|---|---|---|
kestra_url |
Current flow and trigger API base URL | http://localhost:8080 |
kestra_user |
API account name | admin@kestra.io |
kestra_password |
SECRET input filled from KESTRA_PASSWORD; never written to KV |
none |
candidate_url |
Candidate validation API base URL | http://localhost:8080 |
namespace |
Exact namespace to export and scan | company.team |
tenant_id |
API tenant segment | main |
auto_remediate |
Allow trigger quarantine after approval | false |
dry_run |
Prevent writes when true | true |
record_namespace |
KV namespace for audit and rollback records | company.team |
dry_run: true, auto_remediate: false, the target namespace and the candidate URL.auto_remediate: true and dry_run: false only when the listed candidate-invalid triggers are approved in the Pause form.kestra-compat-<execution-id>-disabled-<namespace>-<flow> lists the trigger ids that were enabled; enable those with the Toggle task.Add a candidate image URL or a tenant-aware API authentication block when the deployment uses a different network. To quarantine a narrower set, add a namespace or label filter to inventory and export, then keep the post-toggle verification unchanged.
Limits: The validation endpoint can report only what the candidate image knows about; custom plugins must be installed in the candidate worker before this gate can clear them. Toggle has no conditional write, so another operator who changes a trigger between the final read and the write is caught only by the read-back that follows. The queue serializes executions of this flow, not other writers. Toggle without a trigger id disables every trigger of the flow and the record lists only the ones that were enabled. The gate does not re-enable triggers on its own.
This blueprint was created by zkasuran.
