How to Make GitHub Actions Caches Read-Only for Pull Requests

You want pull requests to reuse your dependency cache without writing new entries into it. Why isn't a restore-only action enough, and what setting actually blocks saves?

How to Make GitHub Actions Caches Read-Only for Pull Requests
TLDR
  • cache-mode: read permits cache restores and blocks saves.
  • Set it for the workflow or an individual job; an explicit job setting takes precedence.
  • Ordinary pull requests and pull_request_target runs have different cache defaults.

Let the PR restore a cache without producing one

Declare cache-mode: read when a PR job should consume existing caches but never save new entries. GitHub made the control generally available on every GitHub.com plan on September 10. The cache service enforces the granted access.

This example shows where the setting belongs. Keep the project's normal installation and test steps after the restore. The cache producer must use a matching key and path.

name: PR cache restore
on: pull_request
cache-mode: read

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/cache/restore@v5
        with:
          path: ~/.npm
          key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
      # Keep your project's install and test steps below.

The restore action has no post-job save step. Choosing that action expresses what the step does; setting the mode restricts what the job can do. A restore-only step alone does not remove another step's ability to attempt a save.

The example uses a hosted runner. For a self-hosted runner, the v5 action requires runner 2.327.1 or newer. Keep a working installation path for cache misses: restoring a package-manager cache is not a replacement for installing the project's dependencies.

Read-only and write-only are different capabilities

Put the key at the top level or under jobs.your_job.cache-mode. An individual job can override the workflow setting. The syntax reference defines four values:

ModeRestoreSave
readAllowedBlocked
writeAllowedAllowed
write-onlyBlockedAllowed
noneBlockedBlocked

For a PR that only consumes a dependency cache, choose read. The name write includes reads; it is not shorthand for write-only.

A normal pull request is not read-only by default

The default restriction covers low-trust events running in the default-branch context, such as pull_request_target. An ordinary pull_request creates caches scoped to its merge ref, so it is outside that restriction. Declare read if you want to prevent those saves too.

Explicitly granting write to a low-trust run overrides its protective default. A trusted push workflow can maintain a shared cache while PR jobs only restore it.

Skipped saves and invalid calls need different fixes

Inspect ACTIONS_CACHE_MODE and the cache-step log. Skipping a cache operation because of the mode does not itself fail the job. Later build steps still need to handle missing cache contents.

With reusable workflows, declare read on the calling job to impose a ceiling. If the callee requests write access beyond that ceiling, GitHub rejects the run with a validation error rather than silently downgrading it.

A caller relying only on a trigger's default read access leaves room for the called workflow to request writes explicitly. Match the caller's declared mode with the callee's requirements before treating the PR pipeline as a read-only cache consumer.