| name | github-actions-cache-optimization |
| description | GitHub Actions cache analysis and optimization. Use when investigating cache bloat, finding stale caches, optimizing keys, or comparing cache usage across repos. |
| user-invocable | false |
| allowed-tools | Bash(gh api *), Bash(gh repo *), Bash(gh cache *), Read, Grep, Glob, TodoWrite |
| created | "2025-01-30T00:00:00.000Z" |
| modified | "2025-01-30T00:00:00.000Z" |
| reviewed | "2025-01-30T00:00:00.000Z" |
GitHub Actions Cache Optimization
Analyze cache usage, identify bloat, and optimize cache strategies for GitHub Actions.
When to Use This Skill
| Use this skill when... | Use X instead when... |
|---|
| Analyzing cache size and count | Investigating workflow run failures → gh-workflow-monitoring |
| Identifying stale or bloated caches | Analyzing billing/minutes → github-actions-finops |
| Optimizing cache key strategies | Setting up new cache actions → github-actions-workflows |
| Cleaning up old caches | General workflow efficiency → github-actions-finops |
Cache Limits
| Limit | Value |
|---|
| Max cache size | 10 GB per repository |
| Max single entry | 10 GB |
| Retention | 7 days without access |
| Eviction | LRU when limit exceeded |
Org-Level Cache Usage
gh api /orgs/$GITHUB_ORG/actions/cache/usage \
--jq '{total_active_caches_count, total_active_caches_size_in_bytes}'
gh api /orgs/$GITHUB_ORG/actions/cache/usage \
--jq '"\(.total_active_caches_count) caches, \(.total_active_caches_size_in_bytes / 1024 / 1024 | floor)MB total"'
Per-Repo Cache Usage
Cache Summary
gh api "/repos/$OWNER/$REPO/actions/cache/usage" \
--jq '{active_caches_count, active_caches_size_in_bytes}'
gh api "/repos/$OWNER/$REPO/actions/cache/usage" \
--jq '"\(.active_caches_count) caches, \(.active_caches_size_in_bytes / 1024 / 1024 | floor)MB"'
Cache List and Breakdown
gh api "/repos/$OWNER/$REPO/actions/caches?per_page=100" \
--jq '.actions_caches[] | "\(.key): \(.size_in_bytes / 1024 / 1024 | floor)MB, last used: \(.last_accessed_at)"'
gh api "/repos/$OWNER/$REPO/actions/caches?per_page=100" \
--jq '.actions_caches | group_by(.key | split("-") | .[0:3] | join("-")) |
map({prefix: .[0].key | split("-") | .[0:3] | join("-"),
count: length,
size_mb: (map(.size_in_bytes) | add / 1024 / 1024 | floor)}) |
sort_by(-.size_mb)'
gh api "/repos/$OWNER/$REPO/actions/caches?per_page=100" \
--jq '.actions_caches | group_by(.ref) |
map({branch: .[0].ref, count: length,
size_mb: (map(.size_in_bytes) | add / 1024 / 1024 | floor)}) |
sort_by(-.size_mb)'
Stale Cache Detection
gh api "/repos/$OWNER/$REPO/actions/caches?per_page=100" \
--jq --arg cutoff "$(date -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ)" \
'.actions_caches[] | select(.last_accessed_at < $cutoff) |
"\(.key): \(.size_in_bytes / 1024 / 1024 | floor)MB, last: \(.last_accessed_at)"'
gh api "/repos/$OWNER/$REPO/actions/caches?per_page=100" \
--jq --arg cutoff "$(date -v-7d +%Y-%m-%dT%H:%M:%SZ)" \
'.actions_caches[] | select(.last_accessed_at < $cutoff) | ...'
Cache Cleanup
Delete Specific Cache
gh api -X DELETE "/repos/$OWNER/$REPO/actions/caches/$CACHE_ID"
gh api -X DELETE "/repos/$OWNER/$REPO/actions/caches?key=$CACHE_KEY"
Bulk Cleanup
gh api "/repos/$OWNER/$REPO/actions/caches?per_page=100&ref=refs/heads/$BRANCH" \
--jq '.actions_caches[].id' | while read id; do
gh api -X DELETE "/repos/$OWNER/$REPO/actions/caches/$id"
done
gh api "/repos/$OWNER/$REPO/actions/caches?per_page=100" \
--jq '.actions_caches[] | select(.key | startswith("PREFIX-")) | .id' | while read id; do
gh api -X DELETE "/repos/$OWNER/$REPO/actions/caches/$id"
done
Cache Bloat Indicators
| Indicator | Threshold | Issue |
|---|
| Total size | >5 GB | Approaching 10GB limit |
| Cache count | >50 | Too many keys/branches |
| Stale caches | >20% older than 7d | Inefficient key strategy |
| Single cache | >2 GB | Consider splitting |
| Branch caches | Many closed PR branches | Missing cleanup workflow |
Cache Key Strategies
Good Key Patterns
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
Problematic Patterns
| Pattern | Issue | Fix |
|---|
${{ github.sha }} in key | Never reused | Use lockfile hash |
${{ github.run_id }} | Never reused | Remove from key |
No restore-keys | Cache misses | Add fallback keys |
| Branch in key | PR cache bloat | Use base branch fallback |
Cache Cleanup Workflow
Add to repository for automatic cleanup:
name: Cache Cleanup
on:
pull_request:
types: [closed]
schedule:
- cron: '0 0 * * 0'
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Cleanup PR caches
if: github.event_name == 'pull_request'
run: |
gh api "/repos/${{ github.repository }}/actions/caches?ref=refs/heads/${{ github.head_ref }}" \
--jq '.actions_caches[].id' | while read id; do
gh api -X DELETE "/repos/${{ github.repository }}/actions/caches/$id" || true
done
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Cleanup stale caches
if: github.event_name == 'schedule'
run: |
# Delete caches not accessed in 14 days
cutoff=$(date -d '14 days ago' +%Y-%m-%dT%H:%M:%SZ)
gh api "/repos/${{ github.repository }}/actions/caches?per_page=100" \
--jq --arg cutoff "$cutoff" \
'.actions_caches[] | select(.last_accessed_at < $cutoff) | .id' | while read id; do
gh api -X DELETE "/repos/${{ github.repository }}/actions/caches/$id" || true
done
Multi-Repo Cache Comparison
for repo in repo1 repo2 repo3; do
echo "=== $repo ==="
gh api "/repos/$GITHUB_ORG/$repo/actions/cache/usage" \
--jq '"\(.active_caches_count) caches, \(.active_caches_size_in_bytes / 1024 / 1024 | floor)MB"'
done
gh repo list $GITHUB_ORG --json nameWithOwner --limit 100 --jq '.[].nameWithOwner' | while read repo; do
size=$(gh api "/repos/$repo/actions/cache/usage" --jq '.active_caches_size_in_bytes // 0' 2>/dev/null)
if [ "$size" -gt 0 ]; then
echo "$repo: $((size / 1024 / 1024))MB"
fi
done | sort -t: -k2 -n -r | head -20
Agentic Optimizations
| Context | Command |
|---|
| Org total | gh api /orgs/$ORG/actions/cache/usage --jq '.total_active_caches_size_in_bytes / 1024 / 1024 | floor' |
| Repo summary | gh api "/repos/$O/$R/actions/cache/usage" --jq '"\(.active_caches_count) caches, \(.active_caches_size_in_bytes / 1048576 | floor)MB"' |
| List caches | gh api "/repos/$O/$R/actions/caches?per_page=30" --jq '.actions_caches[] | "\(.key): \(.size_in_bytes / 1048576 | floor)MB"' |
| By prefix | gh api "..." --jq '.actions_caches | group_by(.key | split("-") | .[0]) | map({prefix: .[0].key | split("-") | .[0], count: length})' |
| Delete cache | gh api -X DELETE "/repos/$O/$R/actions/caches/$ID" |
Quick Reference
| API Endpoint | Method | Purpose |
|---|
/orgs/{org}/actions/cache/usage | GET | Org-wide cache stats |
/repos/{owner}/{repo}/actions/cache/usage | GET | Repo cache stats |
/repos/{owner}/{repo}/actions/caches | GET | List all caches |
/repos/{owner}/{repo}/actions/caches/{id} | DELETE | Delete specific cache |
/repos/{owner}/{repo}/actions/caches?key={key} | DELETE | Delete by key |
See Also
- github-actions-finops - Billing and workflow efficiency analysis
- gh-workflow-monitoring - Watching workflow runs