# GitHub Actions cache-mode Turns CI Caches Into a Governed Resource

- Link: https://thestackobserver.com/github-actions-cache-mode-turns-ci-caches-into-a-governed-resource/
- Published: 2026-09-11T10:16:22-04:00
- Author: Stackxx

GitHub’s new `cache-mode` setting for Actions looks like a small workflow-syntax
addition, but it addresses a problem that has become much harder for platform teams
to ignore: CI caches are shared state, and shared state needs an access model. The
September 10 changelog makes the feature generally available across GitHub plans,
allowing teams to choose whether a workflow or job can restore caches, save caches,
do only one of those operations, or use no cache at all.

The central shift is simple: cache access can now be treated like token permissions.
Instead of assuming every job that invokes a cache action should both read and write,
platform teams can grant cache capability according to trust, purpose, and blast
radius. That matters because a cache is not just a performance optimization. It 
can influence what code runs, what dependency artifacts are reused, and how much
confidence teams can place in CI results for branches, forks, release jobs, and 
reusable workflows.

The practical thesis for DevOps teams is that cache governance should move from 
implicit runner behavior to explicit pipeline policy. The work is to classify jobs
by trust boundary, choose defaults that protect high-value workflows, and accept
that some pipelines should trade a little speed for a clearer security posture.

## What GitHub Changed

`cache-mode` can be set at the workflow level or on an individual job. GitHub lists
four modes: `read`, `write`, `write-only`, and `none`. `read` allows cache restores
but prevents cache saves. `write` allows both restores and saves. `write-only` allows
a job to save a cache but prevents it from restoring one. `none` prevents cache 
access entirely.

The job-level setting overrides the workflow-level setting. That hierarchy is important
because many repositories have one workflow file that mixes jobs with very different
risk profiles: linting a pull request, building a release artifact, running integration
tests, publishing a package, and calling a reusable deployment workflow can all 
sit in the same automation graph.

GitHub says the selected mode is enforced by the cache service through scoped cache
tokens, and that the restriction carries through reusable workflows. A called reusable
workflow cannot receive more cache access than the caller granted. For organizations
that have standardized CI logic through reusable workflows, this is one of the most
significant details. It means a central platform workflow can be useful without 
becoming an accidental privilege escalation path for cache access.

GitHub also calls out low-trust events such as `pull_request_target`. Workflows 
that do not declare a cache mode continue to use existing secure defaults. When 
a low-trust event explicitly grants write access with `write` or `write-only`, Actions
adds a warning annotation because the choice can reintroduce cache-poisoning risk.
That warning is not a substitute for review, but it is a useful signal for repositories
where workflow changes move quickly.

## Why CI Caches Deserve Their Own Permissions

Caches are usually introduced for good reasons. Dependency installs are slow. Language
package managers do repeated work. Monorepos often have a large build graph where
small code changes should not force every toolchain to start from cold storage. 
A cache can turn a frustrating pipeline into a tolerable developer experience.

That performance value is exactly why caches become security-sensitive. Developers
and CI systems tend to trust what makes builds faster. If a poisoned cache can influence
a later job, the attacker has found a place where speed, trust, and automation meet.
The risk is not limited to one language ecosystem or one package manager. Any workflow
that restores precomputed dependencies, compiled objects, tool directories, or generated
state can be affected if the cache key and trust boundary are weak.

Historically, teams managed this risk with naming conventions, careful cache keys,
branch scoping, and review discipline. Those controls still matter, but they are
indirect. `cache-mode` adds a more legible permission layer above that logic.

The analogy to `GITHUB_TOKEN` permissions is useful. Mature Actions users already
avoid broad token permissions when a job only needs read access. Cache access should
follow the same pattern. A test job for an untrusted pull request may benefit from
restoring a known cache but should rarely be allowed to update the cache consumed
by trusted jobs. A release job may not need to restore state created by lower-trust
jobs at all. A cache warm-up job may need to save but not restore. These are different
capabilities, and they now have different words in workflow syntax.

## A Practical Classification Model

The fastest way to misuse the new control is to choose one mode for every workflow
and move on. Platform teams should instead classify jobs along two axes: how much
they trust the trigger, and how much later work trusts the job’s outputs. From there,
cache access becomes a policy decision rather than a local optimization.

For low-trust pull request jobs, `read` is often the right starting point. It preserves
much of the developer-experience benefit by allowing cache restores, while preventing
the job from saving new state. This is especially relevant for public repositories
that accept contributions from forks, but private organizations should not dismiss
the pattern.

For trusted branch builds, `write` may be appropriate when the workflow is responsible
for refreshing caches used by ordinary CI. The key is to make that permission deliberate.
If every push job can write every broadly matched cache key, teams should revisit
cache-key design at the same time they add `cache-mode`. Least privilege does not
help much when all trusted jobs share oversized cache namespaces.

For dedicated cache population jobs, `write-only` is the interesting new option.
It lets a job save fresh cache contents without consuming an existing cache first.
That can be useful when teams want a controlled path for cache updates: run a clean
install, build from known inputs, publish the cache, and prevent earlier cache state
from influencing the result. It is not needed everywhere, but it gives platform 
engineers a cleaner tool for separating cache creation from cache consumption.

For release, signing, deployment, and other high-value jobs, `none` deserves more
consideration than it will probably get. Many sensitive jobs are not performance
bottlenecks compared with the cost of a bad release. If a job signs artifacts, publishes
packages, applies infrastructure changes, or deploys to production, the default 
question should be whether it needs cache access at all. If the answer is no, the
safest cache is no cache.

## Reusable Workflows Need Special Attention

Reusable workflows are one of the places where `cache-mode` can change operational
behavior in useful ways. Organizations often centralize build, test, and deployment
logic so application teams do not have to maintain every detail themselves. That
centralization improves consistency, but it also creates a permissions question:
what should the reusable workflow be allowed to do when called from different contexts?

GitHub’s model gives the caller a ceiling. A reusable workflow cannot receive more
cache access than the caller granted. Platform teams should take advantage of that
by documenting the expected cache behavior of shared workflows and providing examples
for common contexts. A pull request caller might invoke the same reusable test workflow
with `cache-mode: read`. A protected-branch caller might invoke it with `write`.
A release caller might use `none` unless the build process explicitly requires cached
dependencies.

This is also a good moment to audit reusable workflows that hide cache operations
inside central templates. Application teams may not realize that a called workflow
restores or saves caches. With the new setting, platform teams can make that behavior
explicit at the call site, but they still need inventory. Search for cache actions,
package-manager cache flags, and composite actions that wrap caching behavior. Then
decide which callers should be allowed to preserve, update, or bypass that state.

## How To Roll It Out Without Breaking CI

A sensible rollout starts with observation, not mass editing. First, identify workflows
that use `actions/cache`, setup actions with built-in package-manager caching, or
custom composite actions that write cache state. Group them by trigger: pull requests,
pushes to protected branches, scheduled jobs, release tags, deployment workflows,
and reusable workflow callers.

Second, set workflow-level defaults where the answer is obvious. A workflow that
only validates untrusted pull requests can usually start with `cache-mode: read`.
A workflow that exists only to publish a production release may be a candidate for`
cache-mode: none`. Where a workflow contains a mix of jobs, override individual 
jobs that need more access.

Third, watch for silent performance changes. GitHub’s documentation notes that when
a cache operation is not permitted by the effective mode, the cache step logs an
informational message and continues. A skipped restore is treated as a cache miss,
and a skipped save is simply not performed. That behavior is friendly to availability
because it avoids failing workflows during rollout, but it means teams need to monitor
build times and cache-hit rates rather than waiting for red pipelines.

Fourth, update contribution guidance and workflow review checklists. If a contributor
proposes `cache-mode: write` on a low-trust trigger, reviewers should treat that
with the same seriousness as broadening token permissions. GitHub’s warning annotation
helps, but governance still lives in code review, branch protection, and central
workflow ownership.

## The Supply Chain Context

The timing of this feature fits a broader pattern in software delivery security.
Recent supply-chain incidents have shown how quickly legitimate packages and ordinary
install flows can become attack paths. Cloudsmith’s August analysis of compromised`
keyv` and `cacheable` npm packages described malicious versions of real packages
being published into the ecosystem, with install-time lifecycle scripts used to 
harvest credentials and propagate through maintainer accounts. Cloudsmith’s broader
supply-chain guidance argues for policy-enforced control points between public registries,
developers, and pipelines.

GitHub’s `cache-mode` is not an artifact firewall, a malware scanner, or a dependency
review system. It solves a narrower problem inside Actions. But the connection is
operationally important: platform teams are being pushed to govern every place where
untrusted code, third-party packages, credentials, and automation state meet.

That makes cache policy part of supply-chain hygiene. A registry proxy may control
which packages enter the environment. Lockfiles may control versions. Token permissions
may control repository access. Cache permissions now help control whether state 
produced in one workflow context can be saved for another. None of these controls
is sufficient by itself, but together they reduce the number of hidden trust assumptions
in the delivery path.

## Tradeoffs Platform Teams Should Expect

The main tradeoff is performance versus isolation. Restricting cache writes from
pull request workflows may reduce cache freshness. Disabling cache access in sensitive
jobs may add minutes to workflows that developers already consider slow. Using `
write-only` for cache warmers may require more deliberate job design. These costs
are real, and platform teams should measure them.

The counterweight is that cache behavior becomes easier to explain. When an incident
review asks whether an untrusted pull request could have poisoned a cache used by
a protected branch, teams should not have to reconstruct the answer from scattered
YAML and package-manager behavior. They should be able to point to an explicit mode,
a cache-key policy, and a review rule.

## What To Do Next

For organizations using GitHub Actions heavily, the next step is a focused cache
audit. Find the workflows that save cache state, identify which triggers can reach
them, and decide whether the saved state is later consumed by more trusted jobs.
Pay particular attention to public repositories, fork-based contribution models,
monorepos with broad cache keys, and reusable workflows that are called from many
repositories.

Then introduce `cache-mode` as a policy language for those decisions. Use `read`
for low-trust validation paths that should benefit from existing caches but not 
update them. Use `write` where trusted workflows intentionally refresh caches. Use`
write-only` for clean cache population patterns. Use `none` for jobs where cache
state is unnecessary or not worth the additional trust assumption.

The feature is small enough to adopt incrementally, but important enough to include
in platform standards. CI caches used to be treated mostly as a speed knob. GitHub’s
new control makes them look more like what they have become: a governed resource
in the software delivery system.

## Sources

 * [GitHub Changelog: Control GitHub Actions cache access with cache-mode](https://github.blog/changelog/2026-09-10-control-github-actions-cache-access-with-cache-mode)
 * [GitHub Docs: Workflow syntax for cache-mode](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#cache-mode)
 * [Cloudsmith: Keyv and Cacheable npm packages compromised in active supply-chain attack](https://cloudsmith.com/blog/keyv-and-cacheable-npm-packages-compromised-in-active-supply-chain-attack)
 * [Cloudsmith: Trust your software supply chain from ingestion to production](https://cloudsmith.com/blog/trust-your-software-supply-chain-from-ingestion-to-production)
