Audit and Remove GitHub Actions Caches with gh cache
Actions runs keep pulling the same dependency tree fresh, and gh cache tells you whether the cache is stale or just never sticking. You will finish able to inspect caches for a repository and remove one cache, a matching key on one ref, or all caches for a specific ref. The examples use GitHub CLI 2.87.3, the gh executable installed at /home/linuxbrew/.linuxbrew/bin/gh on this machine. Allow about ten minutes for an audit, plus time to decide whether a cache is actually safe to remove.
The route
Jump straight to the step you need, or tick off Done means at the end.
- You need: an authenticated
ghsession and access to the target repository. - What it touches: listing is read-only. Deletion changes remote repository state, needs the
repoauthorisation scope, and cannot be undone throughgh cache. No command in this guide needssudo.
1. Confirm the installed command
Check the binary and its version before relying on option names. This is an ordinary local check:
$ command -v gh
/home/linuxbrew/.linuxbrew/bin/gh
$ gh --version
gh version 2.87.3 (2026-02-23)
The Ubuntu package database on this host also holds a package named gh, but the command found first is the Homebrew binary above. That distinction matters when a flag behaves differently on another machine. Ask the binary you will actually run for the cache help:
$ gh cache --help
Work with GitHub Actions caches.
AVAILABLE COMMANDS
delete: Delete GitHub Actions caches
list: List GitHub Actions caches
Checkpoint
If the version or executable is not the one you expect, stop here and fix your PATH before inspecting or deleting anything.
2. Select the repository explicitly
From a checkout of the intended repository, gh can infer the owner and repository from Git. An explicit repository is safer in a script, or when your current directory is not the checkout:
$ REPO='OWNER/REPOSITORY'
$ gh cache list --repo "$REPO"
- Replace both placeholders with the real owner and repository.
- Enterprise host? Use the documented form
HOST/OWNER/REPO. - Keep it quoted so shell punctuation cannot change the argument.
A successful list prints cache rows. An authentication or permission error means the audit has not happened; do not respond by adding sudo. Check the account and host with gh auth status, then retry the list command.
3. Make a focused cache inventory
gh cache list fetches at most 30 caches by default, sorted by last access with the newest first. Make the selection visible when you are looking for storage that has not been used recently:
$ gh cache list --repo "$REPO" \
--sort last_accessed_at --order asc --limit 100
The limit is the maximum number fetched, not a guarantee that the repository has that many caches. The table includes the cache key, ref, size, creation time and last access time. A cache key is not necessarily unique across refs, so record both the key and ref before deletion.
Filter by a key prefix when a workflow family is known:
$ gh cache list --repo "$REPO" --key 'npm-linux-'
For a branch, use the complete ref form. Do not pass only the short branch name:
$ gh cache list --repo "$REPO" \
--ref 'refs/heads/feature/example' \
--json key,id,ref,sizeInBytes,lastAccessedAt \
--jq '.[] | [.id, .key, .ref, .sizeInBytes, .lastAccessedAt] | @tsv'
Checkpoint
Save or copy the resulting ID, key and ref. An empty result means the filter found no matching cache, not that the repository has no caches at all. Run an unfiltered list before you change the filters.
4. Delete one identified cache
Deletion is the irreversible step. Recheck the repository, ID and key immediately before running it. Prefer an ID copied from the inventory, because a key can match more than one cache:
$ CACHE_ID='123456789'
$ gh cache delete "$CACHE_ID" --repo "$REPO"
The command asks GitHub to remove that cache. The exact confirmation text can vary, but a successful command returns exit status 0. Verify the result by looking up the ID or listing the same filter again:
$ gh cache list --repo "$REPO" --json id,key,ref \
--jq '.[] | select(.id == 123456789)'
No matching JSON output means the ID is no longer in the returned list. If deletion fails, do not repeat it blindly: check the error, authorisation scope and repository target first.
5. Delete a key on one ref
Use a key and full ref when the same workflow key appears on several branches or pull requests:
$ CACHE_KEY='npm-linux-node-22'
$ CACHE_REF='refs/heads/feature/example'
$ gh cache delete "$CACHE_KEY" --ref "$CACHE_REF" --repo "$REPO"
This changes the matching cache for that ref. It does not delete every cache whose key merely looks similar. List with the same key and ref afterwards to confirm what remains.
6. Remove all caches only with a narrow boundary
Warning
--all deletes every cache in the selected repository unless you also supply --ref. This can make the next Actions runs slower while caches rebuild, and there is no restore command in gh cache.
If the intended operation really is to clear one branch or pull request, include its complete ref:
$ CACHE_REF='refs/pull/42/merge'
$ gh cache delete --all --ref "$CACHE_REF" --repo "$REPO"
List that ref again to verify it is empty. For an intentionally broad cleanup, omit --ref only after an unfiltered inventory and a second-person review of the repository name. If no caches exist, the plain --all form returns exit code 1; add --succeed-on-no-caches when an empty result should count as success in a script.
Common traps and recovery
- Short branch names do not work. Use
refs/heads/branch-namefor a branch andrefs/pull/number/mergefor a pull request. - Key prefixes can be ambiguous. A cache key prefix can produce more than one result, so inspect the IDs and refs rather than deleting the first row.
- There is no undo. Recovery means letting a later workflow run recreate the cache, or restoring the workflow's cache configuration if that was the real mistake.
- Deleted mid-run? Let the job finish and check its logs; deletion affects the stored cache, not files already present in a runner.
Done means
- Executable confirmed. The version used by the shell was checked.
- Repository made explicit. You listed the intended repository, using
--repowhere context could be confusing. - Target recorded. You recorded the exact cache ID, key and ref before deletion.
- Full ref used. A full ref was used when removing caches for one branch or pull request.
- Result verified. You confirmed the outcome with a fresh list command.
- Treated as destructive. You know deleted caches must be rebuilt by future workflow runs, not restored.