Delete old GitHub Actions artifacts with PowerShell
My GitHub Actions artifact usage was nearing the maximum quota for the month, so I needed a script to delete old artifacts
I received an email from GitHub overnight saying:
You have used 90% of the Actions storage included for the flcdrg account
Your plan includes 2 GB of Actions storage per month at no extra cost. You have used 90% so far this billing cycle. 1.8 GB used / 2 GB included
Oh dear, that’s not good. Time for some Spring (or Autumn as it is in Australia) cleaning!
I found a useful post by Elio Struyf - Clean up old GitHub Actions artifacts with a script, which contains a Bash script to delete old artifacts. PowerShell is my preferred scripting language so I first asked Copilot to convert the Bash script to PowerShell.
I then ran it on some repositories that I new had lots of artifacts, but noticed that the paging was not working quite right, and that it was skipping artifacts if it couldn’t parse the date field.
I made two changes:
- Read all the data in one go using the
--paginate --slurpparameters. This solves the problem that I think was happening when you read a page of results, then deleted them, and then asked the API for the next page, but the counts would now be out due to the deleted items. - Ensure the date string is parsed using US date format (as it was defaulting to Australian format and then getting confused with dates that didn’t make sense)
Here’s the final script:
param(
[Parameter(Position = 0)]
[string]$Repo,
[Parameter(Position = 1)]
[int]$DaysOld = 5
)
if (-not (Get-Command gh -ErrorAction SilentlyContinue)) {
Write-Error "GitHub CLI (gh) is not installed or not available in PATH."
exit 1
}
$null = gh auth status *> $null
if ($LASTEXITCODE -ne 0) {
Write-Host "Please authenticate with the GitHub CLI using 'gh auth login'."
exit 1
}
if ([string]::IsNullOrWhiteSpace($Repo)) {
Write-Host "Usage: .\delete-github-action-artifacts.ps1 <owner/repo> [days-old]"
Write-Host "Example: .\delete-github-action-artifacts.ps1 owner/repo 5"
exit 1
}
Write-Host "Cleaning up artifacts older than $DaysOld days for repository: $Repo"
$pagesResponse = gh api --paginate --slurp -H "Accept: application/vnd.github+json" "/repos/$Repo/actions/artifacts?per_page=100" | ConvertFrom-Json
$allArtifacts = @()
foreach ($pageResponse in @($pagesResponse)) {
if ($null -ne $pageResponse.artifacts) {
$allArtifacts += @($pageResponse.artifacts)
}
}
if (-not $allArtifacts -or $allArtifacts.Count -eq 0) {
Write-Host "No artifacts found."
}
else {
foreach ($artifact in $allArtifacts) {
$id = $artifact.id
$name = $artifact.name
$createdAt = $artifact.created_at
if ($null -eq $id -or [string]::IsNullOrWhiteSpace($name) -or [string]::IsNullOrWhiteSpace($createdAt)) {
$artifactJson = $artifact | ConvertTo-Json -Compress
Write-Host "Skipping invalid artifact data: $artifactJson"
continue
}
try {
$createdAtUtc = [DateTimeOffset]::Parse($createdAt, [System.Globalization.CultureInfo]::InvariantCulture).UtcDateTime
$ageDays = [int][Math]::Floor(([DateTime]::UtcNow - $createdAtUtc).TotalDays)
if ($ageDays -gt $DaysOld) {
Write-Host "Deleting artifact: $name (ID: $id, Age: $ageDays days)"
$null = gh api -X DELETE "/repos/$Repo/actions/artifacts/$id" 2>$null
if ($LASTEXITCODE -ne 0) {
Write-Host "Failed to delete artifact: $name (ID: $id)"
}
}
else {
Write-Host "Keeping artifact: $name (ID: $id, Age: $ageDays days, Created At: $createdAt)"
}
}
catch {
Write-Host "Deleting artifact: $name (ID: $id, Created At: $createdAt)"
$null = gh api -X DELETE "/repos/$Repo/actions/artifacts/$id" 2>$null
if ($LASTEXITCODE -ne 0) {
Write-Host "Failed to delete artifact: $name (ID: $id)"
}
}
}
}
Write-Host "Cleanup completed."
I’ve also published it as a GitHub Gist at https://gist.github.com/flcdrg/f204fc3f84247fe6247d654c0a673b73
Prevention
The main cause of accumulating artifacts is by using the actions/upload-artifact action in GitHub Actions workflows. I’ve now updated those actions to include the retention-days property so that artifacts are automatically deleted after a few days.
eg.
- name: Upload wrangler.jsonc
uses: actions/upload-artifact@v7
with:
name: wrangler.jsonc
path: wrangler.jsonc
retention-days: 2