Mass Deletion of Github Actions Workflow Runs By Name and Date

4 minute read

Intro

Deleting Github Action workflows the old fashioned way can be painful. I for example had a repository which had old workflows that were no longer used as seen below. These old workflows were in an abundance, in both their name and how many times they were run and deleting them one by one just was not going to cut it.

Old Workflows

My use case and previously made solutions

I knew the answer to this lied within creating a script using the GitHub API, more specifically the workflow runs API. I’ve used GitHub’s API briefly before, but only for testing and reading, not to create or delete.

My objective was clear, I wanted to either get rid of all workflows under a specific name, or to remove workflows before a certain date. In future, I would even want to add the functionality to select multiple workflows and delete en masse.

Before I went into possibly reinventing the wheel, I did a quick google and came across two articles to get me started: DJ Adam’s very handy script “mass-deletion-of-github-actions-workflow-runs” and then to fit my date use case Michael Heap’s post “Filter for dates before today with jq”

I would fully recommend following DJ’s post on the matter, it’s very easy to follow and provides a very good starting point for customising what you need from GitHub’s API. The following is a variation of his script.

Prerequisites

  • GitHub API Authentication, preferably with a PAT.
  • JQ (Json Processor), I’m using macOS so I installed JQ using brew
  • The GitHub CLI
    • If you want to test out your API authentication using GH CLI run this command:
      gh api --method GET /octocat \
      --header 'Accept: application/vnd.github+json' \
      --header "X-GitHub-Api-Version: 2022-11-28"

Creating the script

Viewing your worflows

Using Github’s API alone gives you a lot of data that isn’t necessary, DJ provides a neat solution in using jq to filter down the parameters you want.

gh api /repos/<owner>/<repo>/actions/runs \
 | jq -r '.workflow_runs[] | [.id, .conclusion, .name] | @tsv' \
 | head -5

Which will neatly return:

Recent Workflow Runs

Info

There’s built-in support for pagination with gh api, with the --paginate switch.

The different parts of the jq call are as follows:

Part Description
-r Tells jq to output raw values, rather than JSON structures
.workflows_runs[] Process each of the entries in the workflow runs array
[.id, .conclusion, .name] Show values of these three properties
@tsv Convert everything into tab separated values

Getting all workflow runs by name and date

Here DJ continues to use fzf to allow a user to select multiple workflows to delete in parallel. For my usecase, my primary goal was to completely rid of a specific workflow run, or runs before a certain date.

1while getopts ":n:d:" opt; do
2  case "$opt" in
3    n) select_name=$OPTARG ;;
4    d) select_date=$(date -u -j -f "%Y-%m-%d %H:%M:%S" "$OPTARG" +"%Y-%m-%dT%H:%M:%SZ") ;;
5    # GH CLI uses format: 2024-08-23T17:12:55Z
6    # Humans want to use format "2024-08-23 17:12:55"
7  esac
8done

I started the script by allowing command line arguments for a name and a human usable date. There is room in that script to only require the year month and day for input, but I wanted to be specific and allow input of hour, minute and second.

Filtering runs by name and date

Next was to add in a function to, in a more readable fashion, pipe our jq script to the API call.

 1jqscript() {
 2
 3  cat <<EOF
 4    .workflow_runs[]
 5      | select((.name | startswith("$select_name")) and (.created_at < "$select_date"))
 6      | [
 7          (.conclusion),
 8          (.created_at),
 9          .id,
10          .event,
11          .name
12        ]
13      | @tsv
14EOF
15
16}

Line 5 here being of note, startswith() is used as if a user doesn’t want to filter by a specific name we can pre-set $select_name to blank, where it will fetch all workflow runs.

Then following Michael’s advice, or in my case the inverse of his advice.

jq has built in support for ISO8601 format dates, so 2021-10-02 > 2021-10-02. — michaelheap 1

Deleting runs

Similar to how one can retrieve runs with the GH API using

  • /repos/<owner>/<repo>/actions/runs

one can delete by using:

  • DELETE /repos/<owner>/<repo>/actions/runs/<run_id>

Then it becomes a case of getting the selected runs, and passing them through a delete runs function:

1local run id result
2run=$1
3id="$(cut -f 3 <<< "$run")"
4gh api -X DELETE "/repos/$repo/actions/runs/$id"
5[[ $? = 0 ]] && result="OK!" || result="BAD"
6printf "%s\t%s\n" "$result" "$run")

The end result is this script here:

Feel free to comment on the gist and to use the script!


  1. The above quote is excerpted from Michael’s post on Filter for dates before today with jq↩︎