New to Kestra?
Use blueprints to kickstart your first workflows.
Find Kestra triggers that should be live but are switched off, alert Slack, then behind a Pause approval switch the allowlisted ones back on and verify.
A schedule gets switched off for a good reason. It pages too much during an incident. Or a new guard ships off while someone watches it. Then nobody turns it back on. The schedule never fires again and nothing complains, because the thing that would have complained is the thing that is off. No error, no alert, no execution. Every dashboard looks calm because nothing is running to report. The failure is the absence of work, which is the hardest kind to notice.
This flow reads the trigger state of whichever Kestra you point kestra_url at, finds the
triggers that are off, then switches back on only the ones an operator has written down as
desired_live. Then it reads the state again and fails the run if a trigger it targeted is
still off.
This blueprint was created by zkasuran.
Restarting a schedule somebody stopped on purpose is worse than leaving it off, so the allowlist is the whole safety model, not a convenience.
desired_live as flowId/triggerId. A trigger that is off
and not on the list is reported in the log plus the Slack message, then left exactly as it is.
With the default empty list the flow changes nothing at all.namespace.auto_remediate is true, dry_run is false, the
count is at or under max_enables, a human approves the Pause. Any missing step changes
nothing.Two things turned up while running this against a real Kestra 2.0.5. Both shape the flow.
GET /api/v1/main/triggers/search returns state.disabled, the stored flag an operator flips
in the UI, next to state.sourceDisabled, which mirrors disabled: true in the flow source.
io.kestra.plugin.kestra.triggers.Toggle writes the stored flag only. A source-disabled
trigger stays off whatever the API does, because the fix is a change to the flow file. So the
flow sorts the two apart, switches on the stored-flag ones, then reports the source-disabled
ones as needing a code change instead of pretending to fix them.Toggle logs 0 triggers found to toggle on a write that landed. It reads a count field the
2.0.5 API does not send, so the number is always zero. The re-read is the only trustworthy
signal, which is exactly why this flow fails on the re-read rather than on the task log.detect (io.kestra.plugin.core.http.Request, read-only) calls
GET /api/v1/main/triggers/search with filters[namespace][EQUALS] set to the namespace.
The modern filters[...] form is used on purpose, because the flat namespace= form is
ignored on 2.0.5 and returns every trigger on the instance.select sorts the switched-off triggers into three sets: targets (on the allowlist, stored
flag off, fixable here), blocked (on the allowlist, off in the flow source) plus ignored
(off and not on the allowlist).counts and summary report all three sets with the last time each trigger fired.route (io.kestra.plugin.core.flow.If):clean logs it.alert posts all three sets to Slack.remediate_gate runs only when auto_remediate is true and dry_run is false.act_gate skips straight to nothing_fixable when every off trigger is either source
disabled or not allowlisted.ceiling fails the run rather than acting when more than max_enables are off.approval (io.kestra.plugin.core.flow.Pause) waits for a human. No answer changes nothing.reenable (io.kestra.plugin.core.flow.Loop, concurrencyLimit: 1) switches each one on
with io.kestra.plugin.kestra.triggers.Toggle and enabled: true.reverify reads the trigger state again. verify_gate fails the run if any targeted
trigger is still off. If they are all live it logs the next scheduled run of each one.errors posts to Slack when the target cannot be reached or the verify fails.daily (io.kestra.plugin.core.trigger.Schedule), shipped disabled and in safe mode.The guard is itself a Kestra flow, so watching Kestra needs no extra infrastructure. It runs on a
schedule, posts to Slack, holds the change behind an approval, acts on one trigger at a time,
then keeps the whole decision in the execution history. Point kestra_url at any instance to
check a remote cluster from one place.
On Kestra 2.0.5 OSS. The flow ran on one Kestra and pointed kestra_url at a second throwaway
Kestra, so no real trigger was ever touched. A local HTTP sink stood in for Slack. The target
held five flows with Schedule triggers in one namespace: two switched off through the API and on
the allowlist, one switched off and deliberately NOT on the allowlist, one left enabled and on
the allowlist, one carrying disabled: true in its own source. Before and after values come from
the target's own triggers/search API, not from the flow log.
| Run | Inputs | Result |
|---|---|---|
| Clean | allowlist of one enabled trigger | 0 off, clean branch, run SUCCESS |
| Detect and report | 3 off, dry_run: true |
2 targets, 1 source blocked, 1 not allowlisted, Slack alert sent, all 3 still off afterwards |
| Remediate | auto_remediate: true, dry_run: false |
Pause approved, 2 switched on, re-read found 0 still off, the target API confirmed both stored flags cleared. The non-allowlisted trigger stayed off |
| Work resumed | same run, one target on a one minute cron | the re-enabled trigger fired a real execution within the minute, proven by a new execution on the target plus a moving lastTriggeredDate |
| Nothing fixable | allowlist of only the source-disabled trigger | reported as needing a flow change, no Toggle issued, run SUCCESS |
| Declined | Pause resumed without approval | nothing switched on, both targets still off |
| Ceiling | max_enables: 1 with 2 off |
run failed before any change, both left off |
| Re-verify gate | a copy that also targets the source-disabled trigger | Toggle could not clear it, the re-read caught it, the run failed instead of reporting success |
| Target down | wrong port | detect failed, the errors block posted to Slack |
kestra_url reachable from the Kestra worker. The default http://localhost:8080 works when
the flow checks the cluster it runs on.KESTRA_PASSWORD: the API password, used as the default for the kestra_password input.SLACK_WEBHOOK_URL: used by alert and the errors block.SECRET_.| Input | Default | Purpose |
|---|---|---|
kestra_url |
http://localhost:8080 |
Target Kestra API |
namespace |
company.team |
The one namespace considered |
desired_live |
[] |
Allowlist of flowId/triggerId that must be live |
max_enables |
10 |
Refuse to switch on more than this in one run |
kestra_user |
admin@kestra.io |
API username |
kestra_password |
{{ secret('KESTRA_PASSWORD') }} |
API password |
auto_remediate |
false |
Allow the change |
dry_run |
true |
Must be false for anything to change |
KESTRA_PASSWORD and SLACK_WEBHOOK_URL secrets.desired_live as
flowId/triggerId.auto_remediate: true and dry_run: false, then approve the Pause.daily trigger, which always runs in safe mode.desired_live in Git beside the flows, so the list of schedules that must be live is
reviewed like code.lastTriggeredDate, so a schedule off for
a week pages louder than one off for an hour.kestra_url values to check more than one cluster.