Request icon
OutputValues icon
Log icon
If icon
SlackIncomingWebhook icon
Fail icon
Pause icon
Loop icon
Toggle icon
Sleep icon
Schedule icon

Re-enable Kestra triggers that were never switched back on

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.

Categories
Infrastructure

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.

Safety

Restarting a schedule somebody stopped on purpose is worse than leaving it off, so the allowlist is the whole safety model, not a convenience.

  • It acts ONLY on triggers listed in 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.
  • It looks at one namespace.
  • A change runs only when all of these hold: 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.
  • After the change it reads the trigger state again and fails the run if any targeted trigger is still off.

Findings

Two things turned up while running this against a real Kestra 2.0.5. Both shape the flow.

  • A trigger can be off in two different ways and only one of them is fixable from the API. 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.

How it works

  1. 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.
  2. 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).
  3. counts and summary report all three sets with the last time each trigger fired.
  4. route (io.kestra.plugin.core.flow.If):
    • nothing off: clean logs it.
    • something off: 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.
  5. errors posts to Slack when the target cannot be reached or the verify fails.
  6. daily (io.kestra.plugin.core.trigger.Schedule), shipped disabled and in safe mode.

What you get

  • The triggers in a namespace that are switched off, each with the last time it fired, so a schedule that has not run in weeks is obvious.
  • The ones you declared should be live, switched back on, scoped by an allowlist, approved by a human and capped.
  • The ones that are off in the flow source, named separately, because those need a pull request rather than an API call.
  • A second read of the trigger state as proof, with the next scheduled run of each trigger, so the result is work resuming rather than a flag that moved.

Who it's for

  • Platform teams who disable a noisy schedule during an incident and need something that remembers to turn it back on.
  • Anyone who has found a nightly job that quietly stopped running months ago.
  • Teams who want the remediation step a trigger monitor stops short of, without it restarting something that was paused deliberately.

Why orchestrate this with Kestra

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.

Tested end to end

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

Prerequisites

  • A Kestra API user that can read and update triggers in the namespace.
  • kestra_url reachable from the Kestra worker. The default http://localhost:8080 works when the flow checks the cluster it runs on.

Secrets

  • KESTRA_PASSWORD: the API password, used as the default for the kestra_password input.
  • SLACK_WEBHOOK_URL: used by alert and the errors block.
  • In Kestra OSS, provide each one as a base64-encoded environment variable prefixed SECRET_.

Inputs

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

Quick start

  1. Add the KESTRA_PASSWORD and SLACK_WEBHOOK_URL secrets.
  2. Run it once with the defaults to list every trigger in the namespace that is off.
  3. Take the ones that should be live from that list and put them in desired_live as flowId/triggerId.
  4. Run with auto_remediate: true and dry_run: false, then approve the Pause.
  5. Enable the daily trigger, which always runs in safe mode.

How to extend

  • Open a ticket per source-disabled trigger, since those need a change to the flow file.
  • Keep desired_live in Git beside the flows, so the list of schedules that must be live is reviewed like code.
  • Alert on how long a trigger has been off, read from lastTriggeredDate, so a schedule off for a week pages louder than one off for an hour.
  • Loop over several kestra_url values to check more than one cluster.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.