name: cleanup-actions-artifacts # Manual reclaim of Actions artifact storage for this repository. on: workflow_dispatch: inputs: dry_run: description: List artifacts only; do not delete type: boolean default: false push: branches: [main] paths: - .github/workflows/cleanup-actions-artifacts.yml permissions: actions: write contents: read jobs: cleanup: name: Delete stale Actions artifacts runs-on: ubuntu-latest timeout-minutes: 30 steps: - name: Remove old artifacts env: GH_TOKEN: ${{ github.token }} DRY_RUN: ${{ inputs.dry_run }} REPOSITORY: ${{ github.repository }} run: | python3 <<'PY' import json, os, subprocess, sys repo = os.environ["REPOSITORY"] dry_run = os.environ.get("DRY_RUN", "false").lower() == "true" def gh(*args): return subprocess.check_output(["gh", "api", *args], text=True) def should_delete(name: str) -> bool: if name.startswith("playwright-artifacts-"): return True if name.startswith("digest-"): return True if name == "desktop-e2e-screenshots": return True if name.endswith(".dockerbuild"): return True return False artifacts = [] page = 1 while True: data = json.loads( gh(f"repos/{repo}/actions/artifacts?per_page=100&page={page}") ) artifacts.extend(data.get("artifacts", [])) if len(data.get("artifacts", [])) < 100: break page += 1 deleted = 0 freed = 0 for art in artifacts: name = art["name"] if not should_delete(name): print(f"skip id={art['id']} name={name} size={art['size_in_bytes']}") continue if dry_run: print(f"would delete id={art['id']} name={name} size={art['size_in_bytes']}") continue subprocess.check_call( ["gh", "api", "--method", "DELETE", f"repos/{repo}/actions/artifacts/{art['id']}"] ) print(f"deleted id={art['id']} name={name} size={art['size_in_bytes']}") deleted += 1 freed += art["size_in_bytes"] print(f"deleted_count={deleted}") print(f"freed_bytes={freed}") PY