New to Kestra?
Use blueprints to kickstart your first workflows.
A Kestra blueprint that finds stale GitHub branches, deletes them safely, and posts a Slack summary, with a safe dry-run default.
find_stale (io.kestra.plugin.scripts.python.Script) lists every branch through the GitHub API and decides what is stale. A branch is stale when its pull request was merged, or when it has no PR at all and its last commit is older than stale_days. The default branch, protected branches, and branches with open PRs are never touched. For forks, pass upstream_repo: PRs are looked up on the upstream repo while branches are cleaned on your fork.maybe_clean (io.kestra.plugin.core.flow.If) runs only when something is stale. Inside it, do_clean runs only in live mode (dry_run: false) and deletes each stale branch with the GitHub API. send_summary always posts the report to Slack: what would be deleted in dry-run mode, what was deleted in live mode.dry_run is true unless you explicitly turn it off, and merged-or-abandoned is the only thing that ever gets deleted.Anyone whose repos have accumulated dozens of branches from merged PRs nobody deleted. Works on any GitHub repo you have push access to, personal or org.
A shell script on a cron server can call the GitHub API, but then you need somewhere to hold the token, a place to see the run history when a branch got deleted that should not have been, and a safe dry-run mode you can trust. Kestra gives you all three: the token stays in secrets out of the YAML, every run keeps its logs and outputs where you can audit them, and the dry-run default means the first run can never surprise you.
contents: write on the target repo (classic repo scope works too).| Secret | What it is |
|---|---|
GITHUB_TOKEN |
base64-encoded GitHub personal access token with push access to the repo |
SLACK_WEBHOOK |
base64-encoded Slack Incoming Webhook URL |
In Kestra OSS, secrets come from environment variables prefixed with SECRET_ and must be base64-encoded.
SECRET_ environment variables.repo as owner/repo and dry_run: true: the Slack report lists what would be deleted, and nothing is touched. If you work from a fork, also pass upstream_repo as the upstream owner/repo so merged PRs are found.dry_run: false: the branches are deleted and the Slack report says so.release/*) with a name filter in the Python step.cron trigger.| Input | Type | Default | What it does |
|---|---|---|---|
repo |
STRING | (required) | the repo whose branches to clean, as owner/repo |
upstream_repo |
STRING | "" |
for forks only, the upstream repo as owner/repo. PRs are looked up there, branches are cleaned on repo |
stale_days |
INT | 90 |
branches with no PR and no commit for this long count as abandoned |
dry_run |
BOOL | true |
when true, only reports what would be deleted without deleting anything |
slack_webhook |
STRING | (required) | Incoming webhook URL of the Slack channel for the cleanup report |
dry_run defaults to true for a reason. Flip it to false only after you have seen a dry-run report and you trust the list.