Caching

Cache Scope

Cache scope defines which workflows or identities may discover and restore a cache entry. GitHub Actions applies restrictions involving branches and cache versions, so matching a key alone does not guarantee access to the entry.

Cache scope is the boundary that decides which workflow runs are allowed to read a cache entry. A run reads entries written on its own Git ref plus entries written on the repository's default branch, and entries written anywhere else are refused.

Scope is checked before the key. An entry whose key matches a request exactly still produces a cache miss when that entry sits outside the requesting run's scope.

Definition

Every GitHub Actions cache entry carries three pieces of identity: the repository it belongs to, the Git ref of the run that wrote it, and the key the run supplied. Scope is the repository and ref part of that identity. GitHub describes the rules under restrictions for accessing a cache in the caching documentation, checked on 2026-08-13.

Two boundaries apply, in order.

The repository boundary comes first and has no exceptions. An entry belongs to one repository, and a workflow in a different repository is refused whatever key it asks for. Teams that want one dependency cache across several repositories have to move the storage out of the per repository cache service, which the guide on sharing a build cache across repositories covers.

The ref boundary comes second and is where the surprises live. Runs can restore eligible current-branch and default-branch caches. Pull-request runs can also access caches from their base branch, including a base branch in a fork relationship, subject to GitHub's documented restrictions.

Event that wrote the entryRef recorded on the entryRuns that can read it
Push to the default branchrefs/heads/mainEvery run in the repository
Push to a feature branchrefs/heads/feature-aEligible runs on feature-a, including pull requests; pull requests targeting this base branch
Pull request runrefs/pull/318/mergeLater runs on that same pull request
Tag pushrefs/tags/v1.2.3Later runs on that tag

Default-branch caches are broadly reusable within the repository's allowed scope. A cache created for a pull-request merge ref is narrower: later runs of that same pull request can restore it, but unrelated branches cannot.

Writing a cache under a merge ref does not prevent the pull request from reading eligible head or base branch caches. Cache creation scope and cache restore eligibility are different rules.

Scope decides eligibility only. Inside the right scope a request still has to match on the key and on the entry version, which is derived from the list of cached paths and the compression tool the writing runner used. The cache key page covers that half of the match.

Example

This workflow caches the npm download cache on every push and every pull request. It uses an exact key built from the lock file hash and a prefix fallback:

name: test
on:
  push:
  pull_request:

jobs:
  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - uses: actions/cache@v4
        with:
          path: ~/.npm
          key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
          restore-keys: |
            npm-${{ runner.os }}-
      - run: npm ci
      - run: npm test

Trace five events through a repository whose default branch is main. Lock file hash 9f2c1a is the state on main, and 4b77de is the state after a dependency bump.

OrderEventRef of the runRestore resultEntry written
1Push to mainrefs/heads/mainMiss, cache is emptynpm-Linux-9f2c1a on refs/heads/main
2Push to feature-a, lock file unchangedrefs/heads/feature-aExact hit from the default branchNothing new to write
3Push to feature-a after the bumprefs/heads/feature-aPrefix hit on npm-Linux- from mainnpm-Linux-4b77de on refs/heads/feature-a
4Push to feature-b carrying the same bumprefs/heads/feature-bPrefix hit on main again, the exact entry is out of scopenpm-Linux-4b77de on refs/heads/feature-b
5feature-a merges, push to mainrefs/heads/mainPrefix hitnpm-Linux-4b77de on refs/heads/main

Step 4 is the whole idea. An entry with the exactly correct key already existed when feature-b ran, and feature-b still installed from a stale partial restore, because that entry belonged to feature-a. Step 5 fixes it for everyone: once the bump lands on the default branch, the next run there writes npm-Linux-4b77de in the scope every branch can read.

Reading the scope of live entries

The REST API returns the ref alongside each entry, so the scope of a repository's cache is directly observable:

gh api repos/:owner/:repo/actions/caches \
  --jq '.actions_caches[] | "\(.ref)\t\(.key)\t\(.size_in_bytes)"'
refs/heads/main         npm-Linux-9f2c1a        82134528
refs/heads/feature-a    npm-Linux-4b77de        82397184
refs/heads/feature-b    npm-Linux-4b77de        82397184
refs/pull/318/merge     npm-Linux-4b77de        82397184

Four rows, three of them holding a near identical payload under three refs. Duplicate keys across refs are normal output rather than a fault, and they are the usual reason a repository sits near its storage limit while individual jobs still report misses. gh cache list --ref refs/heads/feature-a narrows the same listing to one scope, which is the fastest way to confirm whether a branch is reading its own entry or borrowing the default branch entry.

FAQ

What decides the scope of a GitHub Actions cache entry?

The Git ref of the run that wrote it, inside the repository that owns it. A push run records refs/heads/<branch>, a pull request run records refs/pull/<number>/merge, and a tag run records refs/tags/<tag>. That ref is the boundary every later restore request is checked against.

Can one feature branch read a cache written by another feature branch?

No. Two feature branches are two separate scopes, so an entry written on feature-a is invisible to feature-b even when the keys match exactly. Both branches can read entries written on the default branch, which is why shared dependencies are usually seeded by a run on main.

Why does a cache stop being found once a pull request is opened?

A pull request can restore eligible head, base, and default-branch caches. Caches it creates use its merge ref and are restricted to that pull request. Check key, version, and scope rather than assuming opening a pull request blocks existing branch caches.

Sources

Definition reviewed .

Start with $10 in free credits

Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.

← Return to glossary