github-actions-cache-optimization

v2026.09.24

GitHub Actions cache analysis and optimization. Use when investigating cache bloat, finding stale caches, optimizing keys, or comparing cache usage across repos.

GitHub
Install command
npx skhub add laurigates/github-actions-cache-optimization
Markdown
SKILL.md

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 countInvestigating workflow run failures → gh-workflow-monitoring
Identifying stale or bloated cachesAnalyzing billing/minutes → github-actions-finops
Optimizing cache key strategiesSetting up new cache actions → github-actions-workflows
Cleaning up old cachesGeneral workflow efficiency → github-actions-finops

Cache Limits

LimitValue
Max cache size10 GB per repository
Max single entry10 GB
Retention7 days without access
EvictionLRU when limit exceeded

Org-Level Cache Usage

# Total cache usage across org
gh api /orgs/$GITHUB_ORG/actions/cache/usage \
  --jq '{total_active_caches_count, total_active_caches_size_in_bytes}'

# Formatted output
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

# Basic cache stats for repo
gh api "/repos/$OWNER/$REPO/actions/cache/usage" \
  --jq '{active_caches_count, active_caches_size_in_bytes}'

# Formatted
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

# List all caches
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)"'

# Group by key prefix (first 3 segments)
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)'

# Caches by branch
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

# Caches not accessed in 7+ days (candidates for cleanup)
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)"'

# macOS date variant
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

# Delete cache by ID
gh api -X DELETE "/repos/$OWNER/$REPO/actions/caches/$CACHE_ID"

# Delete cache by key (exact match)
gh api -X DELETE "/repos/$OWNER/$REPO/actions/caches?key=$CACHE_KEY"

Bulk Cleanup

# Delete all caches for a specific branch
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

# Delete caches matching key prefix
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

IndicatorThresholdIssue
Total size>5 GBApproaching 10GB limit
Cache count>50Too many keys/branches
Stale caches>20% older than 7dInefficient key strategy
Single cache>2 GBConsider splitting
Branch cachesMany closed PR branchesMissing cleanup workflow

Cache Key Strategies

Good Key Patterns

# OS + lockfile hash (recommended)
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}

# With restore keys for partial matches
restore-keys: |
  ${{ runner.os }}-node-

# Include tool version
key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}

Problematic Patterns

PatternIssueFix
${{ github.sha }} in keyNever reusedUse lockfile hash
${{ github.run_id }}Never reusedRemove from key
No restore-keysCache missesAdd fallback keys
Branch in keyPR cache bloatUse 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'  # Weekly

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
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Multi-Repo Cache Comparison

# Compare cache usage across repos
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

# Full org scan
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

ContextCommand
Org totalgh api /orgs/$ORG/actions/cache/usage --jq '.total_active_caches_size_in_bytes / 1024 / 1024 | floor'
Repo summarygh api "/repos/$O/$R/actions/cache/usage" --jq '"\(.active_caches_count) caches, \(.active_caches_size_in_bytes / 1048576 | floor)MB"'
List cachesgh api "/repos/$O/$R/actions/caches?per_page=30" --jq '.actions_caches[] | "\(.key): \(.size_in_bytes / 1048576 | floor)MB"'
By prefixgh api "..." --jq '.actions_caches | group_by(.key | split("-") | .[0]) | map({prefix: .[0].key | split("-") | .[0], count: length})'
Delete cachegh api -X DELETE "/repos/$O/$R/actions/caches/$ID"

Quick Reference

API EndpointMethodPurpose
/orgs/{org}/actions/cache/usageGETOrg-wide cache stats
/repos/{owner}/{repo}/actions/cache/usageGETRepo cache stats
/repos/{owner}/{repo}/actions/cachesGETList all caches
/repos/{owner}/{repo}/actions/caches/{id}DELETEDelete specific cache
/repos/{owner}/{repo}/actions/caches?key={key}DELETEDelete by key

See Also

  • github-actions-finops - Billing and workflow efficiency analysis
  • gh-workflow-monitoring - Watching workflow runs
Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

MIT

Source path

finops-plugin/skills/github-actions-cache-optimization

Default branch

main

Latest commit

1668324

Tree SHA

b2d4cc3