Skip to content

Bulk Delete GitHub Actions Workflow Runs

GitHub Actions accumulates workflow run history quickly. After a few months of active development, a busy repository can have thousands of runs: every push, every pull request, every scheduled job. Most of those runs are no longer useful, but the GitHub web interface only lets you delete one run at a time. If you want to clean up a large backlog, you need a different approach.

This post covers a one-liner that bulk-deletes all workflow runs for a repository using the GitHub CLI (gh) and jq.

Why You Might Want to Do This

A few common reasons to clear out old workflow run history:

  • A past run captured a secret or sensitive value in its logs, and you want to remove it.
  • A repository was renamed, transferred, or repurposed, and the old run history is noise.
  • You are tidying up before open-sourcing a project that previously had a private CI history.
  • You simply want a clean slate after a period of heavy churn.

GitHub does not offer a bulk-delete option in the UI, and the REST API only supports deleting one run per request. Scripting it is the practical solution.

Prerequisites

You need two tools installed and available on your PATH.

Install the GitHub CLI

brew install gh
sudo apt update && sudo apt install -y gh
sudo dnf install -y gh
winget install GitHub.cli

After installation, authenticate with GitHub:

gh auth login

Run gh auth status to confirm your session is active before continuing.

Install jq

brew install jq
sudo apt update && sudo apt install -y jq
sudo dnf install -y jq
winget install jqlang.jq

The Script

REPO="OWNER/REPO"
LIMIT=1000

gh run list --repo "$REPO" --limit "$LIMIT" --json databaseId | \
jq -r '.[].databaseId' | \
while read run_id; do
  echo "Deleting workflow run: $run_id"
  gh run delete "$run_id" --repo "$REPO" <<< "y"
done

Set REPO to the full repository path, for example REPO="tenthirtyam/setup-task". Adjust LIMIT as needed (the maximum accepted by gh run list is 1000).

How It Works

  1. gh run list --repo "$REPO" --limit "$LIMIT" --json databaseId fetches up to $LIMIT workflow runs for the repository, returning only the databaseId field for each run.
  2. jq -r '.[].databaseId' extracts those IDs as plain text, one per line.
  3. The while read loop iterates over each ID and calls gh run delete to remove it. The <<< "y" automatically confirms the deletion prompt so the loop runs unattended.

Handling More Than 1,000 Runs

The --limit flag on gh run list caps at 1,000. If the repository has more runs than that, run the script multiple times until the history is empty, or adjust the loop to run until gh run list returns an empty result:

REPO="OWNER/REPO"
LIMIT=1000

while true; do
  ids=$(gh run list --repo "$REPO" --limit "$LIMIT" --json databaseId | jq -r '.[].databaseId')
  [ -z "$ids" ] && break
  echo "$ids" | while read run_id; do
    echo "Deleting workflow run: $run_id"
    gh run delete "$run_id" --repo "$REPO" <<< "y"
  done
done

Targeting a Specific Workflow

To delete runs for a single workflow rather than the entire repository, add the --workflow flag with the workflow file name:

REPO="OWNER/REPO"
WORKFLOW="ci.yml"
LIMIT=1000

gh run list --repo "$REPO" --workflow "$WORKFLOW" --limit "$LIMIT" --json databaseId | \
jq -r '.[].databaseId' | \
while read run_id; do
  echo "Deleting workflow run: $run_id"
  gh run delete "$run_id" --repo "$REPO" <<< "y"
done

A Note on Permissions

Deleting workflow runs requires write access to the repository. If you are running this against an organization repository, confirm that your token or GitHub App has the actions:write permission before running the script.