Dependency caching reference

Find information on the functionality of dependency caching in workflows.

cache action usage

The cache action will attempt the following sequence when restoring a cache:

  1. First, it searches for an exact match to your provided key.
  2. If no exact match is found, it will search for partial matches of the key.
  3. If there is still no match found, and you've provided restore-keys, these keys will be checked sequentially for partial matches. For more information, see Cache key matching.

If there is an exact match to the provided key, this is considered a cache hit. If no cache exactly matches the provided key, this is considered a cache miss. On a cache miss, the action automatically creates a new cache if the job completes successfully. The new cache will use the key you provided and contains the files you specify in path. For more information about how this is handled, see Cache hits and misses.

You cannot change the contents of an existing cache. Instead, you can create a new cache with a new key.

Input parameters for the cache action

Note

We recommend that you don't store any sensitive information, such as access tokens or login credentials, in files in the cache path. Anyone with read access can create a pull request on a repository and access the contents of a cache. Additionally, forks of a repository can create pull requests on the base branch and access caches on the base branch.

Output parameters for the cache action

Cache hits and misses

When key exactly matches an existing cache, it's called a cache hit, and the action restores the cached files to the path directory.

When key doesn't match an existing cache, it's called a cache miss, and a new cache is automatically created if the job completes successfully.

When a cache miss occurs, the action also searches your specified restore-keys for any matches:

  1. If you provide restore-keys, the cache action sequentially searches for any caches that match the list of restore-keys.
  2. The cache action completes and the next step in the job runs.
  3. If the job completes successfully, the action automatically creates a new cache with the contents of the path directory.

For a more detailed explanation of the cache matching process, see Cache key matching.

Example using the cache action

This example creates a new cache when the packages in package-lock.json file change, or when the runner's operating system changes. The cache key uses contexts and expressions to generate a key that includes the runner's operating system and a SHA-256 hash of the package-lock.json file.

name: Caching with npm
on: push
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - name: Cache node modules
        id: cache-npm
        uses: actions/cache@v4
        env:
          cache-name: cache-node-modules
        with:
          # npm cache files are stored in `~/.npm` on Linux/macOS
          path: ~/.npm
          key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-build-${{ env.cache-name }}-
            ${{ runner.os }}-build-
            ${{ runner.os }}-

      - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }}
        name: List the state of node modules
        continue-on-error: true
        run: npm list

      - name: Install dependencies
        run: npm install

      - name: Build
        run: npm run build

      - name: Test
        run: npm test

Using contexts to create cache keys

A cache key can include any of the contexts, functions, literals, and operators supported by GitHub Actions. For more information, see Contexts reference and Evaluate expressions in workflows and actions.

Using expressions to create a key allows you to automatically create a new cache when dependencies change.

For example, you can create a key using an expression that calculates the hash of an npm package-lock.json file. So, when the dependencies that make up the package-lock.json file change, the cache key changes and a new cache is automatically created.

npm-${{ hashFiles('package-lock.json') }}

GitHub evaluates the expression hash "package-lock.json" to derive the final key.

npm-d5ea0750

Using the output of the cache action

You can use the output of the cache action to do something based on whether a cache hit or miss occurred. When an exact match is found for a cache for the specified key, the cache-hit output is set to true.

In the example workflow above, there is a step that lists the state of the Node modules if a cache miss occurred:

- if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }}
  name: List the state of node modules
  continue-on-error: true
  run: npm list

Cache key matching

The cache action first searches for cache hits for key and the cache version in the branch containing the workflow run. If there is no hit, it searches for prefix-matches for key, and if there is still no hit, it searches for restore-keys and the version. If there are still no hits in the current branch, the cache action retries the same steps on the default branch. Please note that the scope restrictions apply during the search. For more information, see Restrictions for accessing a cache.

Cache version is a way to stamp a cache with metadata of the path and the compression tool used while creating the cache. This ensures that the consuming workflow run uniquely matches a cache it can actually decompress and use. For more information, see Cache Version in the Actions Cache documentation.

restore-keys allows you to specify a list of alternate restore keys to use when there is a cache miss on key. You can create multiple restore keys ordered from the most specific to least specific. The cache action searches the restore-keys in sequential order. When a key doesn't match directly, the action searches for keys prefixed with the restore key. If there are multiple partial matches for a restore key, the action returns the most recently created cache.

Example using multiple restore keys

restore-keys: |
  npm-feature-${{ hashFiles('package-lock.json') }}
  npm-feature-
  npm-

The runner evaluates the expressions, which resolve to these restore-keys:

restore-keys: |
  npm-feature-d5ea0750
  npm-feature-
  npm-

The restore key npm-feature- matches any key that starts with the string npm-feature-. For example, both of the keys npm-feature-fd3052de and npm-feature-a9b253ff match the restore key. The cache with the most recent creation date would be used. The keys in this example are searched in the following order:

  1. npm-feature-d5ea0750 matches a specific hash.
  2. npm-feature- matches cache keys prefixed with npm-feature-.
  3. npm- matches any keys prefixed with npm-.

Example of search priority

key:
  npm-feature-d5ea0750
restore-keys: |
  npm-feature-
  npm-

For example, if a pull request contains a feature branch and targets the default branch (main), the action searches for key and restore-keys in the following order:

  1. Key npm-feature-d5ea0750 in the feature branch
  2. Key npm-feature- in the feature branch
  3. Key npm- in the feature branch
  4. Key npm-feature-d5ea0750 in the main branch
  5. Key npm-feature- in the main branch
  6. Key npm- in the main branch

setup-* actions for specific package managers

If you are caching the package managers listed below, using their respective setup-* actions requires minimal configuration and will create and restore dependency caches for you.

Package managers setup-* action for caching
npm, Yarn, pnpm setup-node
pip, pipenv, Poetry setup-python
Gradle, Maven setup-java
RubyGems setup-ruby
Go go.sum setup-go
.NET NuGet setup-dotnet

Restrictions for accessing a cache

Access restrictions provide cache isolation and security by creating a logical boundary between different branches or tags.
Workflow runs can restore caches created in either the current branch or the default branch (usually main). If a workflow run is triggered for a pull request, it can also restore caches created in the base branch, including base branches of forked repositories. For example, if the branch feature-b has the base branch feature-a, a workflow run triggered on a pull request would have access to caches created in the default main branch, the base feature-a branch, and the current feature-b branch.

Workflow runs cannot restore caches created for child branches or sibling branches. For example, a cache created for the child feature-b branch would not be accessible to a workflow run triggered on the parent main branch. Similarly, a cache created for the feature-a branch with the base main would not be accessible to its sibling feature-c branch with the base main. Workflow runs also cannot restore caches created for different tag names. For example, a cache created for the tag release-a with the base main would not be accessible to a workflow run triggered for the tag release-b with the base main.

When a cache is created by a workflow run triggered on a pull request, the cache is created for the merge ref (refs/pull/.../merge). Because of this, the cache will have a limited scope and can only be restored by re-runs of the pull request. It cannot be restored by the base branch or other pull requests targeting that base branch.

Multiple workflow runs in a repository can share caches. A cache created for a branch in a workflow run can be accessed and restored from another workflow run for the same repository and branch.

Cache access for low-trust workflow triggers

Some workflows run in response to events that can be initiated by people who do not have write access to the repository, such as a fork pull request or an issue comment. When these events run in the context of the default branch, they could be used to write a malicious cache that a later, more privileged workflow restores and trusts. This class of attack is known as cache poisoning.

To reduce this risk, only these workflow triggers can create or overwrite caches in the default branch’s scope:

Runs triggered by any other event that resolves to the default branch are given read-only access to caches in the default branch's scope. These runs can restore existing caches but cannot create or overwrite them. This includes triggers whose payload or initiating actor can be influenced by someone outside the repository, such as pull_request_target, issue_comment, and workflow_run. A repository can opt out of this restriction for a specific workflow or job by explicitly declaring a write-capable cache-mode. See Bypassing the default untrusted-trigger cache restriction.

The pull_request event is not affected. Caches created by a pull_request run are already scoped to the merge ref (refs/pull/.../merge) and cannot be written to the default branch's scope. For more information, see Restrictions for accessing a cache.

When a run with read-only cache access tries to save a cache, the save fails but the step and the job do not. The workflow continues, and the failure is reported as a warning in the workflow log. In that case, consider the following:

Controlling cache access with cache-mode

Use the cache-mode workflow key to grant jobs the least amount of cache access they need. You can set cache-mode at the workflow level, at the job level, or both. A job-level value overrides the workflow-level value for that job. For the syntax, see Workflow syntax for GitHub Actions.

cache-mode controls the cache access granted to the job's token, and it is enforced with scoped cache tokens. The key accepts the following values.

Value Restore caches Save caches
read Yes No
write Yes Yes
write-only No Yes
none No No

If you omit cache-mode, a read or write default is used based on the trigger type. See Defaults and Bypassing the default untrusted-trigger cache restriction.

Defaults

Configuration Trigger type Effective access
cache-mode omitted Trusted write
cache-mode omitted Low-trust read
cache-mode: write Trusted or low-trust write
cache-mode: write-only Trusted or low-trust write-only
cache-mode: read Trusted or low-trust read
cache-mode: none Trusted or low-trust none

For the trusted-versus-low-trust trigger breakdown, see Cache access for low-trust workflow triggers.

The runner exposes the effective mode in the ACTIONS_CACHE_MODE environment variable, and the actions/cache action and the @actions/cache toolkit honor it. Restore is skipped when the mode does not allow reads (none or write-only), and save is skipped when the mode does not allow writes (none or read). When a cache operation is skipped because of the mode, the action logs an informational message and the step and run continue without failing. A skipped restore is treated as a cache miss; a skipped save is simply not performed.

Cache access in reusable workflows

cache-mode propagates from a caller workflow to the reusable workflows it calls. An explicit cache-mode on the calling job, or inherited from the caller workflow, limits the cache access the called workflow can request.

If the calling job neither sets nor inherits an explicit cache-mode, the called workflow can explicitly request write even when the caller's low-trust trigger defaults to read. To cap a called workflow at read-only access, set cache-mode: read on the job that calls it.

If a called workflow declares a cache-mode that requests access beyond this explicit limit, the run does not start and GitHub reports a validation error. For example, a caller that allows at most read cannot call a workflow that declares write. Because read and write-only grant different, non-overlapping capabilities, a mismatch between them is also an over-request. For example, a write-only caller cannot call a workflow that declares read. For more information about calling reusable workflows, see Reuse workflows.

Bypassing the default untrusted-trigger cache restriction

A job or workflow that explicitly declares cache-mode: write or cache-mode: write-only overrides the secure read-only default that would otherwise apply to a run triggered by a low-trust event. See Cache access for low-trust workflow triggers.

Warning

Explicitly declaring cache-mode: write or cache-mode: write-only reintroduces the risk of cache-poisoning that the default untrusted-trigger read-only permissions are designed to prevent. If a workflow that runs on an untrusted trigger, such as pull_request_target, issue_comment, or workflow_run, declares a write-capable cache-mode, vulnerabilities or untrusted code execution in the workflow could then be used to save a cache. A more privileged workflow that later restores that cache could then execute attacker-controlled content.

Before declaring a write-capable cache-mode on a workflow with a low-trust trigger, consider whether a narrower mitigation meets your needs instead:

Best practices for using caches securely

Cache contents are not signed or verified, and any workflow run that can read a cache may extract its contents. Extracted caches may modify files that are subsequently executed in a workflow run, leading to malicious code execution. Follow these practices to reduce the security risk of using caches.

For broader guidance on securing your workflows, see Secure use reference.

Usage limits and eviction policy

GitHub applies limits to cache storage and retention to manage storage costs and prevent abuse. Understanding these limits helps you optimize your cache usage.

Default limits

GitHub will remove any cache entries that have not been accessed in over 7 days. There is no limit on the number of caches you can store, but the total size of all caches in a repository is limited. By default, the limit is 10 GB per repository, but this limit can be increased by enterprise owners, organization owners, or repository administrators. Any usage beyond 10 GB is billed to your account. Once a repository has reached its maximum cache storage, the cache eviction policy will create space by deleting the caches in order of last access date, from oldest to most recent.

If you exceed the limit, GitHub will save the new cache but will begin evicting caches until the total size is less than the repository limit. The cache eviction process may cause cache thrashing, where caches are created and deleted at a high frequency. To reduce this, you can review the caches for a repository and take corrective steps, such as removing caching from specific workflows or increasing your cache size. This functionality is only available to users with a payment method on file who opt in by configuring cache settings. See Managing caches.

You can create cache entries at a rate of up to 200 uploads per minute per repository, and download them at a rate of 1500 downloads per minute per repository. If you exceed this rate, subsequent cache upload or download attempts will fail until the relevant rate limit resets. The time until the rate limit resets is returned in the Retry-After header of the response. See Actions limits for more information about GitHub Actions rate limits.

Increasing cache size

If you want to reduce the rate at which cache entries are evicted, you can increase the storage limits for your cache in the Actions Settings. Repositories owned by users can configure up to 10 TB per repository. For repositories owned by organizations, the maximum configurable limit is determined by the organization's settings. For organizations owned by an enterprise, the maximum configurable limit is determined by the enterprise's settings. Increasing the limit beyond the default 10 GB will incur additional costs, if that storage is used.

For more information, see:

Usage of additional storage is also controlled by budgets set for GitHub Actions or the Actions Cache Storage SKU. If you have limits configured, and you exceed a budget, your cache will become read-only until your billing status is resolved, or your usage goes beneath the free limit of 10GB by caches expiring or being explicitly deleted. For more information on how to set up budgets, see Setting up budgets to control spending on metered products.

Setting your Actions Cache Storage SKU budgets lower than the total cost of using your configured storage over your billing period can lead to your cache frequently going into read-only mode. For example, if your budget for the SKU is $0, and you've configured your repository's maximum cache size at 20GB, your cache will enter read-only mode as soon as storage exceeds the free threshold.

Below are some illustrative monthly costs to inform budgets you may wish to set for the Actions Cache Storage SKU.

Cache size Monthly cost (if fully utilized)
50GB $2.80
200GB $13.30
1000GB $69.30

Next steps

To manage your dependency caches, see Managing caches.