Skip to content

feat(cache): add cache-mode client behavior (read-denied warning + ACTIONS_CACHE_MODE skip)#2447

Draft
philip-gai wants to merge 11 commits into
mainfrom
philip-gai/cache-read-denied-warning
Draft

feat(cache): add cache-mode client behavior (read-denied warning + ACTIONS_CACHE_MODE skip)#2447
philip-gai wants to merge 11 commits into
mainfrom
philip-gai/cache-read-denied-warning

Conversation

@philip-gai

@philip-gai philip-gai commented Jul 1, 2026

Copy link
Copy Markdown
Member

Description

This PR adds client-side cache-mode behavior to @actions/cache. It combines two related pieces of work:

  • Surface the service-side read-denied policy as a non-fatal warning.
  • Honor a new ACTIONS_CACHE_MODE environment variable to skip cache operations the effective mode does not permit.

Cache-mode values are none | read | write | write-only (hyphenated). It is a partial lattice, not linear: readable modes = {read, write}, writable modes = {write, write-only}, none = neither. The ACTIONS_CACHE_MODE env var is provided by the Actions runner, and is gated on the service side. When it is unset or unrecognized, behavior is identical to today (regression-safe).

Read-denied warning

When the cache service denies a download because the token has no readable scopes, the run should continue with a clear warning instead of a confusing failure.

  • Added CacheReadDeniedError and the shared cache read denied: prefix constant.
  • On the v2 restore path, the denial arrives as a wrapped 403; the restore dispatch detects the prefix and surfaces it as a core.warning, then returns a cache miss.
  • Matches the existing write-denied handling (cache write denied:) for consistency.

ACTIONS_CACHE_MODE skip

Skip operations the effective cache-mode does not permit, before any tar or network work.

  • Skip restore when the mode is not readable (none, write-only).
  • Skip save when the mode is not writable (none, read).
  • Restore skip returns a cache miss (undefined); save skip returns the existing not-saved sentinel (-1). Neither throws.
  • Exactly one core.info line per skip; extra detail (paths, key) is behind ACTIONS_STEP_DEBUG.
  • Unset or unrecognized modes are permissive, so behavior is unchanged.

Rationale for skipping restore on write-only: write-only tokens have no read scope, so a restore would be denied service-side and trip the read-denied warning. Skipping client-side avoids the wasted round-trip and gives a clearer message.

Companion changes

  • actions/runner exports ACTIONS_CACHE_MODE into the step environment.

Notes

  • packages/cache/RELEASES.md updated under the 6.2.0 entry.
  • Test coverage: full cache suite passing, including the read-denied dispatch and a cache-mode truth table (none/read/write/write-only plus unset and unknown regression cases).

https://github.com/github/actions-persistence/issues/1168

Mirror the existing cache write-denied handling on the restore path. When
the receiver refuses a download URL because the run's token has no readable
cache scopes, it returns a twirp PermissionDenied (HTTP 403). The twirp
client wraps that 403 in a generic Error, so the stable 'cache read denied:'
prefix is embedded in the message rather than at the start.

- Add CACHE_READ_DENIED_PREFIX and CacheReadDeniedError
- Dispatch on the prefix in the restoreCacheV2 catch block (V2 only), log a
  policy-specific warning, and report a cache miss so the run continues
- Add a test mirroring the write-denied coverage
Re-throw CacheReadDeniedError from an inner try/catch around
GetCacheEntryDownloadURL and dispatch on typedError.name in the outer catch,
matching how saveCacheV2 handles CacheWriteDeniedError.
Extend the read-denied handling to Cache Service v1 so GHES (which forces v1
via _apis/artifactcache) is covered when read-scope enforcement ships there.

- Surface the receiver's error body message from getCacheEntry instead of a
  generic status-code error, so the cache read denied: prefix reaches callers
- Re-throw CacheReadDeniedError from restoreCacheV1 and dispatch on it in the
  outer catch, mirroring restoreCacheV2 and the write-denied v1 handling
- Add a v1 read-denied test

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves cache restore resilience by detecting policy-driven cache read denials (insufficient read permissions) and surfacing them as a clear core.warning while treating the restore as a cache miss so workflows continue.

Changes:

  • Added read-denial detection via CACHE_READ_DENIED_PREFIX and a dedicated CacheReadDeniedError for targeted handling.
  • Updated both cache restore implementations (v1 REST + v2 Twirp/gRPC) to reclassify read-denied failures into a single warning and continue as a miss.
  • Adjusted v1 getCacheEntry to selectively surface the receiver’s denial message and added tests + release/version bumps.
Show a summary per file
File Description
packages/cache/src/internal/cacheHttpClient.ts Surfaces receiver error message only for cache read denied: so restore paths can reclassify it.
packages/cache/src/cache.ts Adds read-denied prefix + error type and handles read-denied restores as warning + cache miss for both v1/v2.
packages/cache/tests/restoreCache.test.ts Adds v1 tests asserting warning behavior and cache-miss semantics for read denial.
packages/cache/tests/restoreCacheV2.test.ts Adds v2 test asserting warning behavior and cache-miss semantics for wrapped read-denied errors.
packages/cache/tests/cacheHttpClient.test.ts Adds tests ensuring getCacheEntry only surfaces body for read-denied cases.
packages/cache/RELEASES.md Documents the 6.2.0 behavior change.
packages/cache/package.json Bumps @actions/cache version to 6.2.0.
packages/cache/package-lock.json Updates lockfile version metadata to 6.2.0.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Files not reviewed (1)
  • packages/cache/package-lock.json: Generated file
  • Files reviewed: 7/8 changed files
  • Comments generated: 1
  • Review effort level: Low

Comment thread packages/cache/src/cache.ts Outdated
@philip-gai philip-gai changed the title Handle cache read error due to insufficient read permissions feat(cache): add cache-mode client behavior (read-denied warning + ACTIONS_CACHE_MODE skip) Jul 2, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Files not reviewed (1)
  • packages/cache/package-lock.json: Generated file
  • Files reviewed: 11/12 changed files
  • Comments generated: 1
  • Review effort level: Low

Comment thread packages/cache/src/cache.ts Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants