New to Kestra?
Use blueprints to kickstart your first workflows.
Find Kestra executions stuck PAUSED past a grace window, alert Slack, then behind an allowlist and a Pause approval answer each one and verify it resumed.
An execution that hit a Pause waiting for a human answer, then never got one, sits PAUSED for
ever. It holds its concurrency budget. Its downstream never runs. Because the execution is not
FAILED no failure alert fires. Weeks later nobody remembers what it was waiting for. This
flow finds those executions, reports them, then answers them under conditions you set.
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 guards itself.
This blueprint was created by zkasuran.
Resuming a pause means answering an approval nobody answered. A blind resume can approve a destructive action that a human deliberately walked away from. This flow is for pauses abandoned by accident, never for approving work on somebody's behalf. The allowlist is what keeps those two apart.
allow_flows is empty by default, so out of the box nothing is ever resumed. You name the
flows whose pauses you have decided are safe to answer, one flow id at a time. A paused
execution of any other flow is reported in the logs, in Slack, then left alone.min_paused_minutes is a grace window. A pause younger than the window is somebody's live
decision, not an abandoned one, so it is never a candidate.required_pause_task pins the task id the candidate must be waiting on. A candidate paused
somewhere else is skipped, the re-verify then sees it still PAUSED, so the run fails rather
than passing quietly.resume_answer is the answer you wrote, sent verbatim as the pause's onResume payload. The
flow never invents an approval value.max_resumes refuses the batch rather than answering it when the pile is large. A large pile
means approvals are not being answered at all, which is a process problem.auto_remediate true, dry_run false, the flow on
allow_flows, the count at or under max_resumes, a human approving this flow's own Pause.
Any missing step changes nothing.window freezes the grace-window cutoff once, so every later comparison uses the same
instant instead of drifting between tasks.detect (io.kestra.plugin.kestra.executions.Query, read-only) fetches every PAUSED
execution in the namespace with fetchType: FETCH. Its rows carry state.histories, which
is where the moment an execution went PAUSED comes from. Execution start date is the wrong
field here: a long run that paused a minute ago started hours ago.paused_total (io.kestra.plugin.kestra.executions.Count, read-only) asks the same cluster
for its own PAUSED count, so the detail query is checked against the target rather than
trusted.select and pick map each row to its PAUSED timestamp, drop this flow plus this execution,
keep what is past the cutoff, then narrow that to allow_flows.counts emits the ids, the stale detail, the allowlisted detail plus what the allowlist held
back. summary logs all of it, which is the whole output in dry run.route (io.kestra.plugin.core.flow.If):clean logs how many pauses are inside the window.alert posts to Slack.report_only logs what would be answered unless auto_remediate is true with dry_run
false.eligible_gate stops at none_eligible when the allowlist leaves nothing.ceiling fails the run rather than answering when more than max_resumes are eligible.approval (io.kestra.plugin.core.flow.Pause) waits for a human. No answer changes
nothing.recover (io.kestra.plugin.core.flow.Loop, concurrencyLimit: 1) reads each execution
with detail, checks required_pause_task in task_gate, then answers it with
io.kestra.plugin.kestra.executions.Resume carrying your resume_answer.settle, then reverify queries PAUSED again and verify_counts intersects that with
the ids targeted.verify_gate fails the run when any targeted execution is still PAUSED. It logs
recovered when none is.onResume payload is your JSON, not a hardcoded approval.Platform teams running Kestra with human-in-the-loop approvals. If any flow of yours pauses for a release sign-off, a data-quality sign-off or a cost sign-off, some of those pauses are already stale and nothing is telling you. Also for anyone who has watched a scheduled flow queue behind a pause from three weeks ago.
The detection, the approval gate plus the write all live in one versioned flow, against Kestra's
own typed plugin rather than curl. The resume is itself gated by a Pause, so answering
somebody else's approval needs an approval, which is the shape this problem deserves. Kestra
keeps the full run history, so every recovery has a record of who approved it, what answer was
sent, which executions moved. The schedule means you find the stale pauses rather than
remembering to look.
Run against Kestra 2.0.5 on a dedicated instance, never a shared one, with two flows pausing on named approval tasks.
clean logged.dry_run=true: the stale pause named in the log plus Slack, both executions still PAUSED
afterwards, confirmed by id through the target's own API.auto_remediate=true, dry_run=false with an approved Pause: the allowlisted execution went
PAUSED to SUCCESS, its onResume answer arrived as sent, the re-query found it gone.Pause: nothing resumed, target still PAUSED.max_resumes: 1 failed the run before the approval.required_pause_task set to a task the candidate is not on left it PAUSED,
the re-query caught it, the run FAILED.kestra_url fired the errors: Slack block.SLACK_WEBHOOK_URL: incoming webhook for the alert plus the failure notice.KESTRA_PASSWORD: password for the API user on the target cluster. Default of the
kestra_password input, so it is never written into the flow.The API user needs read on executions in the namespace plus permission to resume them.
kestra_url: target Kestra API base URL. Defaults to the local cluster.namespace: the only namespace considered.min_paused_minutes: grace window before a pause counts as abandoned. Default 60.allow_flows: flow ids whose pauses may be answered. Empty by default, which resumes nothing.required_pause_task: expected paused task id. Empty skips the task check.resume_answer: JSON object sent as the onResume answer. Default {"approved": true}.max_resumes: ceiling per run. Default 5.kestra_user, kestra_password: API credentials.auto_remediate, dry_run: the write gate. Defaults are report-only.SLACK_WEBHOOK_URL plus KESTRA_PASSWORD to your namespace secrets.dry_run=true. You get the list of abandoned pauses, nothing changes.allow_flows.allow_flows, set required_pause_task to the approval task id, set
resume_answer to the answer that flow's onResume expects.auto_remediate=true plus dry_run=false, then answer the approval pause with
approved: true. Check the recovered log line.hourly trigger once you are happy. It runs report-only, so it alerts without
touching anything.io.kestra.plugin.notifications.pagerduty or Opsgenie when a pause
older than a day is an on-call matter.io.kestra.plugin.kestra.executions.Kill for
pauses whose approval window has closed, where cancelling is safer than approving.io.kestra.plugin.jdbc.postgresql.Query so the audit
trail outlives the execution retention.io.kestra.plugin.kestra.namespaces.NamespacesWithFlows plus a loop over
namespaces, keeping one allowlist per namespace.