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

Reap Kestra executions stuck in RUNNING

Find Kestra executions stuck in RUNNING past a threshold, alert Slack, then behind a Pause approval kill each one and verify it is gone.

Categories
Infrastructure

When a worker dies mid task, its execution can sit in RUNNING for ever. Nothing fails and no alert fires. The execution keeps holding a concurrency slot, so the next scheduled run queues behind a job that will never finish. A failure alert cannot catch this, because the execution never failed. This flow finds those stuck executions. It is the piece most monitors leave out: it does the kill, behind an approval, then proves the kill landed.

It talks to the Kestra API of whichever instance you point kestra_url at. The default is the cluster the flow runs on, so it reaps itself.

This blueprint was created by zkasuran.

Safety

Killing executions is destructive, so the scope is narrow by default and the kill is gated.

  • It only looks at one namespace. The flow_id input can narrow it to a single flow.
  • It never kills its own execution or any other run of this reaper flow.
  • exclude_flows lists flows that are allowed to run long.
  • A kill runs only when all of these hold: auto_remediate is true, dry_run is false, the count is at or under max_kills, a human approves the Pause. Any missing step keeps everything.
  • After the kills it reads the API again and fails the run if any targeted execution is still RUNNING.

How it works

  1. detect (io.kestra.plugin.core.http.Request, read-only) calls GET /api/v1/main/executions/search with filters[state][EQUALS]=RUNNING, filters[namespace][EQUALS] and filters[startDate][LESS_THAN_OR_EQUAL_TO] set to now minus the threshold. The modern filters[...] form is used on purpose: the flat state=/namespace= form is ignored on 2.0.5 and returns everything, which is the wrong answer for a kill.
  2. select and counts drop this reaper's own flow and execution, apply the flow_id filter and exclude_flows, then emit the ids and a one-line detail of what is stuck.
  3. summary logs how many are stuck and how many were selected.
  4. route (io.kestra.plugin.core.flow.If):
    • nothing stuck: clean logs it.
    • stuck found: alert posts to Slack.
      • remediate_gate runs only when auto_remediate is true and dry_run is false.
      • ceiling fails the run rather than killing when more than max_kills are stuck.
      • approval (io.kestra.plugin.core.flow.Pause) waits for a human. No answer keeps everything.
      • reap (io.kestra.plugin.core.flow.Loop, concurrencyLimit: 1) kills each id with io.kestra.plugin.kestra.executions.Kill.
      • reverify reads the API again and verify_gate fails if any targeted id is still RUNNING.
  5. errors posts to Slack when the API cannot be reached.
  6. hourly (io.kestra.plugin.core.trigger.Schedule), shipped disabled and in safe mode.

What you get

  • The executions in a namespace that have been RUNNING longer than you allow, with the flow id and the time each started.
  • A kill that is scoped, approved and capped, so it cannot run away.
  • A second read of the API as proof, so a kill that silently failed shows up as a failed run rather than a false all-clear.

Who it's for

  • Platform teams whose workers crash or get evicted and leave executions hanging.
  • Anyone who has seen a schedule stop firing because a dead run still holds the concurrency slot.
  • Teams who want the remediation step a plain stuck-execution monitor stops short of.

Why orchestrate this with Kestra

The reaper is itself a Kestra flow, so it needs no extra infrastructure to watch Kestra. It runs on a schedule, reports to Slack, holds the kill behind an approval, kills one execution at a time, then keeps a full execution history of every decision. Point kestra_url at any instance to reap a remote cluster from one place.

Tested end to end

On Kestra 2.0.5 OSS. To keep real executions safe the reaper pointed at a second Kestra 2.0.5 as the target, with a local HTTP sink standing in for Slack. The target held one throwaway flow whose only task sleeps for 30 minutes, so every run of it sits in RUNNING. I seeded several and drove each path. Before and after counts come from the target's own executions API, not only the flow log.

Run Inputs Result
Clean threshold 120 min 0 stuck, clean branch, run SUCCESS
Detect and report threshold 0, dry_run: true 4 stuck found and listed, Slack alert sent, nothing killed, the 4 still RUNNING afterwards
Remediate threshold 0, auto_remediate: true, dry_run: false 4 found, Pause approved, 4 killed, the re-query found 0 still RUNNING, the target API confirmed all 4 KILLED. RUNNING went from 4 to 0
Ceiling threshold 0, max_kills: 2 with 3 stuck run failed before any kill, the 3 left RUNNING
Re-verify gate kill step disabled in a copy the 3 stayed RUNNING, the re-query caught them, 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 search and kill executions in the namespace.
  • kestra_url reachable from the Kestra worker. The default http://localhost:8080 works when the flow reaps 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, the cluster to reap
namespace company.team The one namespace considered
flow_id empty Narrow to a single flow, empty means every flow in the namespace
max_running_minutes 30 A run older than this while RUNNING is stuck
exclude_flows [] Flows allowed to run long
max_kills 25 Refuse to kill 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 kill
dry_run true Must be false for a kill to run

Quick start

  1. Add the KESTRA_PASSWORD and SLACK_WEBHOOK_URL secrets.
  2. Set namespace, then point kestra_user at a user that can kill executions.
  3. Run it once with the defaults and read the list of stuck executions.
  4. When you want it to act, run with auto_remediate: true and dry_run: false, then approve the Pause.
  5. Enable the hourly trigger, which always runs in safe mode.

How to extend

  • Open an incident ticket per stuck execution, instead of or alongside the Slack alert.
  • Restart the owning flow after a kill, so the work actually gets done.
  • Loop over several kestra_url values to reap more than one cluster.
  • Match the namespace by prefix to cover child namespaces.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.