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

Merged
philip-gai merged 15 commits into
mainfrom
philip-gai/cache-read-denied-warning
Jul 13, 2026
Merged

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

Conversation

@philip-gai

@philip-gaiphilip-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

CopilotAI 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
FileDescription
packages/cache/src/internal/cacheHttpClient.tsSurfaces receiver error message only for cache read denied: so restore paths can reclassify it.
packages/cache/src/cache.tsAdds read-denied prefix + error type and handles read-denied restores as warning + cache miss for both v1/v2.
packages/cache/tests/restoreCache.test.tsAdds v1 tests asserting warning behavior and cache-miss semantics for read denial.
packages/cache/tests/restoreCacheV2.test.tsAdds v2 test asserting warning behavior and cache-miss semantics for wrapped read-denied errors.
packages/cache/tests/cacheHttpClient.test.tsAdds tests ensuring getCacheEntry only surfaces body for read-denied cases.
packages/cache/RELEASES.mdDocuments the 6.2.0 behavior change.
packages/cache/package.jsonBumps @actions/cache version to 6.2.0.
packages/cache/package-lock.jsonUpdates 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 threadpackages/cache/src/cache.ts Outdated
@philip-gaiphilip-gai changed the title Handle cache read error due to insufficient read permissionsfeat(cache): add cache-mode client behavior (read-denied warning + ACTIONS_CACHE_MODE skip)Jul 2, 2026

CopilotAI 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 threadpackages/cache/src/cache.ts Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@philip-gai
philip-gai marked this pull request as ready for review July 8, 2026 14:58
@philip-gai
philip-gai requested a review from a team as a code ownerJuly 8, 2026 14:58
jasongin
jasongin previously approved these changes Jul 9, 2026
Comment threadpackages/cache/src/internal/config.ts Outdated
jasongin
jasongin previously approved these changes Jul 10, 2026
Comment threadpackages/cache/__tests__/config.test.ts
Comment threadpackages/cache/__tests__/config.test.ts
Comment threadpackages/cache/__tests__/restoreCache.test.ts Outdated
Comment threadpackages/cache/__tests__/restoreCache.test.ts Outdated
Comment threadpackages/cache/__tests__/saveCache.test.ts Outdated
Comment threadpackages/cache/src/internal/cacheHttpClient.ts
Comment threadpackages/cache/src/cache.ts Outdated
Comment threadpackages/cache/src/cache.ts Outdated
Comment threadpackages/cache/src/cache.ts
…denied handling
Address PR review feedback:
- Merge the duplicate restore/save skip test.each blocks into single blocks parametrized over ACTIONS_CACHE_SERVICE_V2.
- Drop the redundant CacheReadDeniedError catch arms; the typed error is not an HttpClientError so it already falls through to a non-fatal warning.
- Clarify why read-denied classification happens both in getCacheEntry and cache.ts (dependency-free internal module cannot import the typed error).
Mirror the read-denied simplification on the save path. CacheWriteDeniedError
is not an HttpClientError and its name does not match the ReserveCacheError
arm, so it falls through to the same non-fatal warning. Logging behavior is
unchanged (warns, never fails the run) and the exported type is still thrown
internally for consumers and tests. Also refresh stale doc wording.
The two restoreCache tests exercised the identical warning + cache-miss path
now that read-denied is no longer reclassified in the catch, so merge them into
one. The read-denied prefix detection that actually branches on the message is
covered by getCacheEntry tests in cacheHttpClient.test.ts.

@boxofyellowboxofyellow left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Looks good to me.

@philip-gai
philip-gai merged commit ffdc20e into mainJul 13, 2026
25 of 27 checks passed
@philip-gai
philip-gai deleted the philip-gai/cache-read-denied-warning branch July 13, 2026 15:03
philip-gai added a commit that referenced this pull request Jul 15, 2026
#2451)
Backport of the read-denied and ACTIONS_CACHE_MODE cache-mode gating from
the ESM v6.2.0 line (#2447) to the CommonJS v5 line, released as 5.2.0.
Mirrors the earlier write-denied backport (#2435, 5.1.0).
- Detect the `cache read denied:` prefix on download failures (v2 twirp
path and v1 `_apis/artifactcache` path) and surface it as a core.warning
without failing the run.
- Honor ACTIONS_CACHE_MODE: skip restore when the effective cache-mode does
not permit reads (none, write-only) and skip save when it does not permit
writes (none, read), logging a single non-fatal core.info line. Unset or
unrecognized modes are unchanged.
- Add read-denied and cache-mode tests; bump to 5.2.0 with RELEASES entry.
Copilot-Session: e96deec1-716e-4e14-acdf-a230139420a2
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
zanieb added a commit to astral-sh/uv that referenced this pull request Aug 18, 2026
Trusted release actions must not accidentally restore a poisoned shared
GitHub cache. Set `ACTIONS_CACHE_MODE: none` in `release.yml` and each
reusable workflow it calls so clients that support the variable skip
cache reads and writes. Each reusable workflow needs its own setting
because workflow-level environment variables do not propagate to called
workflows. The shared binary-build workflow uses the same cache-disabled
setting when invoked by CI.
This is a cooperative client-side default, not token revocation or
server-enforced denial. Older or independent clients, or code calling
the cache API directly, can still access the service. The
`@actions/cache` library's [6.2.0
change](actions/toolkit#2447) skips operations
before contacting the cache backend; that is separate from its handling
of server-denied requests. The release graph uses `setup-uv@v10.0.1` and
`setup-python@v7.0.0`, whose pinned
[setup-uv](https://github.com/astral-sh/setup-uv/blob/20cfd1bf945f4377ade1205e4dbc17946fc9a30d/package-lock.json)
and
[setup-python](https://github.com/actions/setup-python/blob/5fda3b95a4ea91299a34e894583c3862153e4b97/package-lock.json)
dependencies include `@actions/cache@6.2.0`. Both honor this variable.
The separate `actions/cache@v6.1.0` action still bundles
`@actions/cache@6.1.0` and does not honor it; the release graph does not
invoke that action directly.
Stacked on astral-sh#761, which keeps the action-specific
opt-outs, removes the docs Rust cache, and explicitly disables maturin's
sccache integration. `release-prepare.yml`, same-run artifacts, and
Depot's own cache are unchanged.
Co-authored-by: Zanie Blue <contact@zanie.dev>
Sign up for freeto 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.

4 participants

@philip-gai@jasongin@boxofyellow
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

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

Merged
philip-gai merged 15 commits into
mainfrom
philip-gai/cache-read-denied-warning
Jul 13, 2026
Merged

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

Conversation

@philip-gai

@philip-gaiphilip-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

CopilotAI 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
FileDescription
packages/cache/src/internal/cacheHttpClient.tsSurfaces receiver error message only for cache read denied: so restore paths can reclassify it.
packages/cache/src/cache.tsAdds read-denied prefix + error type and handles read-denied restores as warning + cache miss for both v1/v2.
packages/cache/tests/restoreCache.test.tsAdds v1 tests asserting warning behavior and cache-miss semantics for read denial.
packages/cache/tests/restoreCacheV2.test.tsAdds v2 test asserting warning behavior and cache-miss semantics for wrapped read-denied errors.
packages/cache/tests/cacheHttpClient.test.tsAdds tests ensuring getCacheEntry only surfaces body for read-denied cases.
packages/cache/RELEASES.mdDocuments the 6.2.0 behavior change.
packages/cache/package.jsonBumps @actions/cache version to 6.2.0.
packages/cache/package-lock.jsonUpdates 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 threadpackages/cache/src/cache.ts Outdated
@philip-gaiphilip-gai changed the title Handle cache read error due to insufficient read permissionsfeat(cache): add cache-mode client behavior (read-denied warning + ACTIONS_CACHE_MODE skip)Jul 2, 2026

CopilotAI 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 threadpackages/cache/src/cache.ts Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@philip-gai
philip-gai marked this pull request as ready for review July 8, 2026 14:58
@philip-gai
philip-gai requested a review from a team as a code ownerJuly 8, 2026 14:58
jasongin
jasongin previously approved these changes Jul 9, 2026
Comment threadpackages/cache/src/internal/config.ts Outdated
jasongin
jasongin previously approved these changes Jul 10, 2026
Comment threadpackages/cache/__tests__/config.test.ts
Comment threadpackages/cache/__tests__/config.test.ts
Comment threadpackages/cache/__tests__/restoreCache.test.ts Outdated
Comment threadpackages/cache/__tests__/restoreCache.test.ts Outdated
Comment threadpackages/cache/__tests__/saveCache.test.ts Outdated
Comment threadpackages/cache/src/internal/cacheHttpClient.ts
Comment threadpackages/cache/src/cache.ts Outdated
Comment threadpackages/cache/src/cache.ts Outdated
Comment threadpackages/cache/src/cache.ts
…denied handling
Address PR review feedback:
- Merge the duplicate restore/save skip test.each blocks into single blocks parametrized over ACTIONS_CACHE_SERVICE_V2.
- Drop the redundant CacheReadDeniedError catch arms; the typed error is not an HttpClientError so it already falls through to a non-fatal warning.
- Clarify why read-denied classification happens both in getCacheEntry and cache.ts (dependency-free internal module cannot import the typed error).
Mirror the read-denied simplification on the save path. CacheWriteDeniedError
is not an HttpClientError and its name does not match the ReserveCacheError
arm, so it falls through to the same non-fatal warning. Logging behavior is
unchanged (warns, never fails the run) and the exported type is still thrown
internally for consumers and tests. Also refresh stale doc wording.
The two restoreCache tests exercised the identical warning + cache-miss path
now that read-denied is no longer reclassified in the catch, so merge them into
one. The read-denied prefix detection that actually branches on the message is
covered by getCacheEntry tests in cacheHttpClient.test.ts.

@boxofyellowboxofyellow left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Looks good to me.

@philip-gai
philip-gai merged commit ffdc20e into mainJul 13, 2026
25 of 27 checks passed
@philip-gai
philip-gai deleted the philip-gai/cache-read-denied-warning branch July 13, 2026 15:03
philip-gai added a commit that referenced this pull request Jul 15, 2026
#2451)
Backport of the read-denied and ACTIONS_CACHE_MODE cache-mode gating from
the ESM v6.2.0 line (#2447) to the CommonJS v5 line, released as 5.2.0.
Mirrors the earlier write-denied backport (#2435, 5.1.0).
- Detect the `cache read denied:` prefix on download failures (v2 twirp
path and v1 `_apis/artifactcache` path) and surface it as a core.warning
without failing the run.
- Honor ACTIONS_CACHE_MODE: skip restore when the effective cache-mode does
not permit reads (none, write-only) and skip save when it does not permit
writes (none, read), logging a single non-fatal core.info line. Unset or
unrecognized modes are unchanged.
- Add read-denied and cache-mode tests; bump to 5.2.0 with RELEASES entry.
Copilot-Session: e96deec1-716e-4e14-acdf-a230139420a2
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
zanieb added a commit to astral-sh/uv that referenced this pull request Aug 18, 2026
Trusted release actions must not accidentally restore a poisoned shared
GitHub cache. Set `ACTIONS_CACHE_MODE: none` in `release.yml` and each
reusable workflow it calls so clients that support the variable skip
cache reads and writes. Each reusable workflow needs its own setting
because workflow-level environment variables do not propagate to called
workflows. The shared binary-build workflow uses the same cache-disabled
setting when invoked by CI.
This is a cooperative client-side default, not token revocation or
server-enforced denial. Older or independent clients, or code calling
the cache API directly, can still access the service. The
`@actions/cache` library's [6.2.0
change](actions/toolkit#2447) skips operations
before contacting the cache backend; that is separate from its handling
of server-denied requests. The release graph uses `setup-uv@v10.0.1` and
`setup-python@v7.0.0`, whose pinned
[setup-uv](https://github.com/astral-sh/setup-uv/blob/20cfd1bf945f4377ade1205e4dbc17946fc9a30d/package-lock.json)
and
[setup-python](https://github.com/actions/setup-python/blob/5fda3b95a4ea91299a34e894583c3862153e4b97/package-lock.json)
dependencies include `@actions/cache@6.2.0`. Both honor this variable.
The separate `actions/cache@v6.1.0` action still bundles
`@actions/cache@6.1.0` and does not honor it; the release graph does not
invoke that action directly.
Stacked on astral-sh#761, which keeps the action-specific
opt-outs, removes the docs Rust cache, and explicitly disables maturin's
sccache integration. `release-prepare.yml`, same-run artifacts, and
Depot's own cache are unchanged.
Co-authored-by: Zanie Blue <contact@zanie.dev>
Sign up for freeto 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.

4 participants

@philip-gai@jasongin@boxofyellow
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

Merged
philip-gai merged 15 commits into
mainfrom
philip-gai/cache-read-denied-warning
Jul 13, 2026
Merged

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

Conversation

@philip-gai

@philip-gaiphilip-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

CopilotAI 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
FileDescription
packages/cache/src/internal/cacheHttpClient.tsSurfaces receiver error message only for cache read denied: so restore paths can reclassify it.
packages/cache/src/cache.tsAdds read-denied prefix + error type and handles read-denied restores as warning + cache miss for both v1/v2.
packages/cache/tests/restoreCache.test.tsAdds v1 tests asserting warning behavior and cache-miss semantics for read denial.
packages/cache/tests/restoreCacheV2.test.tsAdds v2 test asserting warning behavior and cache-miss semantics for wrapped read-denied errors.
packages/cache/tests/cacheHttpClient.test.tsAdds tests ensuring getCacheEntry only surfaces body for read-denied cases.
packages/cache/RELEASES.mdDocuments the 6.2.0 behavior change.
packages/cache/package.jsonBumps @actions/cache version to 6.2.0.
packages/cache/package-lock.jsonUpdates 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 threadpackages/cache/src/cache.ts Outdated
@philip-gaiphilip-gai changed the title Handle cache read error due to insufficient read permissionsfeat(cache): add cache-mode client behavior (read-denied warning + ACTIONS_CACHE_MODE skip)Jul 2, 2026

CopilotAI 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 threadpackages/cache/src/cache.ts Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@philip-gai
philip-gai marked this pull request as ready for review July 8, 2026 14:58
@philip-gai
philip-gai requested a review from a team as a code ownerJuly 8, 2026 14:58
jasongin
jasongin previously approved these changes Jul 9, 2026
Comment threadpackages/cache/src/internal/config.ts Outdated
jasongin
jasongin previously approved these changes Jul 10, 2026
Comment threadpackages/cache/__tests__/config.test.ts
Comment threadpackages/cache/__tests__/config.test.ts
Comment threadpackages/cache/__tests__/restoreCache.test.ts Outdated
Comment threadpackages/cache/__tests__/restoreCache.test.ts Outdated
Comment threadpackages/cache/__tests__/saveCache.test.ts Outdated
Comment threadpackages/cache/src/internal/cacheHttpClient.ts
Comment threadpackages/cache/src/cache.ts Outdated
Comment threadpackages/cache/src/cache.ts Outdated
Comment threadpackages/cache/src/cache.ts
…denied handling
Address PR review feedback:
- Merge the duplicate restore/save skip test.each blocks into single blocks parametrized over ACTIONS_CACHE_SERVICE_V2.
- Drop the redundant CacheReadDeniedError catch arms; the typed error is not an HttpClientError so it already falls through to a non-fatal warning.
- Clarify why read-denied classification happens both in getCacheEntry and cache.ts (dependency-free internal module cannot import the typed error).
Mirror the read-denied simplification on the save path. CacheWriteDeniedError
is not an HttpClientError and its name does not match the ReserveCacheError
arm, so it falls through to the same non-fatal warning. Logging behavior is
unchanged (warns, never fails the run) and the exported type is still thrown
internally for consumers and tests. Also refresh stale doc wording.
The two restoreCache tests exercised the identical warning + cache-miss path
now that read-denied is no longer reclassified in the catch, so merge them into
one. The read-denied prefix detection that actually branches on the message is
covered by getCacheEntry tests in cacheHttpClient.test.ts.

@boxofyellowboxofyellow left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Looks good to me.

@philip-gai
philip-gai merged commit ffdc20e into mainJul 13, 2026
25 of 27 checks passed
@philip-gai
philip-gai deleted the philip-gai/cache-read-denied-warning branch July 13, 2026 15:03
philip-gai added a commit that referenced this pull request Jul 15, 2026
#2451)
Backport of the read-denied and ACTIONS_CACHE_MODE cache-mode gating from
the ESM v6.2.0 line (#2447) to the CommonJS v5 line, released as 5.2.0.
Mirrors the earlier write-denied backport (#2435, 5.1.0).
- Detect the `cache read denied:` prefix on download failures (v2 twirp
path and v1 `_apis/artifactcache` path) and surface it as a core.warning
without failing the run.
- Honor ACTIONS_CACHE_MODE: skip restore when the effective cache-mode does
not permit reads (none, write-only) and skip save when it does not permit
writes (none, read), logging a single non-fatal core.info line. Unset or
unrecognized modes are unchanged.
- Add read-denied and cache-mode tests; bump to 5.2.0 with RELEASES entry.
Copilot-Session: e96deec1-716e-4e14-acdf-a230139420a2
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
zanieb added a commit to astral-sh/uv that referenced this pull request Aug 18, 2026
Trusted release actions must not accidentally restore a poisoned shared
GitHub cache. Set `ACTIONS_CACHE_MODE: none` in `release.yml` and each
reusable workflow it calls so clients that support the variable skip
cache reads and writes. Each reusable workflow needs its own setting
because workflow-level environment variables do not propagate to called
workflows. The shared binary-build workflow uses the same cache-disabled
setting when invoked by CI.
This is a cooperative client-side default, not token revocation or
server-enforced denial. Older or independent clients, or code calling
the cache API directly, can still access the service. The
`@actions/cache` library's [6.2.0
change](actions/toolkit#2447) skips operations
before contacting the cache backend; that is separate from its handling
of server-denied requests. The release graph uses `setup-uv@v10.0.1` and
`setup-python@v7.0.0`, whose pinned
[setup-uv](https://github.com/astral-sh/setup-uv/blob/20cfd1bf945f4377ade1205e4dbc17946fc9a30d/package-lock.json)
and
[setup-python](https://github.com/actions/setup-python/blob/5fda3b95a4ea91299a34e894583c3862153e4b97/package-lock.json)
dependencies include `@actions/cache@6.2.0`. Both honor this variable.
The separate `actions/cache@v6.1.0` action still bundles
`@actions/cache@6.1.0` and does not honor it; the release graph does not
invoke that action directly.
Stacked on astral-sh#761, which keeps the action-specific
opt-outs, removes the docs Rust cache, and explicitly disables maturin's
sccache integration. `release-prepare.yml`, same-run artifacts, and
Depot's own cache are unchanged.
Co-authored-by: Zanie Blue <contact@zanie.dev>
Sign up for freeto 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.

4 participants

@philip-gai@jasongin@boxofyellow
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

Merged
philip-gai merged 15 commits into
mainfrom
philip-gai/cache-read-denied-warning
Jul 13, 2026
Merged

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

Conversation

@philip-gai

@philip-gaiphilip-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

CopilotAI 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
FileDescription
packages/cache/src/internal/cacheHttpClient.tsSurfaces receiver error message only for cache read denied: so restore paths can reclassify it.
packages/cache/src/cache.tsAdds read-denied prefix + error type and handles read-denied restores as warning + cache miss for both v1/v2.
packages/cache/tests/restoreCache.test.tsAdds v1 tests asserting warning behavior and cache-miss semantics for read denial.
packages/cache/tests/restoreCacheV2.test.tsAdds v2 test asserting warning behavior and cache-miss semantics for wrapped read-denied errors.
packages/cache/tests/cacheHttpClient.test.tsAdds tests ensuring getCacheEntry only surfaces body for read-denied cases.
packages/cache/RELEASES.mdDocuments the 6.2.0 behavior change.
packages/cache/package.jsonBumps @actions/cache version to 6.2.0.
packages/cache/package-lock.jsonUpdates 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 threadpackages/cache/src/cache.ts Outdated
@philip-gaiphilip-gai changed the title Handle cache read error due to insufficient read permissionsfeat(cache): add cache-mode client behavior (read-denied warning + ACTIONS_CACHE_MODE skip)Jul 2, 2026

CopilotAI 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 threadpackages/cache/src/cache.ts Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@philip-gai
philip-gai marked this pull request as ready for review July 8, 2026 14:58
@philip-gai
philip-gai requested a review from a team as a code ownerJuly 8, 2026 14:58
jasongin
jasongin previously approved these changes Jul 9, 2026
Comment threadpackages/cache/src/internal/config.ts Outdated
jasongin
jasongin previously approved these changes Jul 10, 2026
Comment threadpackages/cache/__tests__/config.test.ts
Comment threadpackages/cache/__tests__/config.test.ts
Comment threadpackages/cache/__tests__/restoreCache.test.ts Outdated
Comment threadpackages/cache/__tests__/restoreCache.test.ts Outdated
Comment threadpackages/cache/__tests__/saveCache.test.ts Outdated
Comment threadpackages/cache/src/internal/cacheHttpClient.ts
Comment threadpackages/cache/src/cache.ts Outdated
Comment threadpackages/cache/src/cache.ts Outdated
Comment threadpackages/cache/src/cache.ts
…denied handling
Address PR review feedback:
- Merge the duplicate restore/save skip test.each blocks into single blocks parametrized over ACTIONS_CACHE_SERVICE_V2.
- Drop the redundant CacheReadDeniedError catch arms; the typed error is not an HttpClientError so it already falls through to a non-fatal warning.
- Clarify why read-denied classification happens both in getCacheEntry and cache.ts (dependency-free internal module cannot import the typed error).
Mirror the read-denied simplification on the save path. CacheWriteDeniedError
is not an HttpClientError and its name does not match the ReserveCacheError
arm, so it falls through to the same non-fatal warning. Logging behavior is
unchanged (warns, never fails the run) and the exported type is still thrown
internally for consumers and tests. Also refresh stale doc wording.
The two restoreCache tests exercised the identical warning + cache-miss path
now that read-denied is no longer reclassified in the catch, so merge them into
one. The read-denied prefix detection that actually branches on the message is
covered by getCacheEntry tests in cacheHttpClient.test.ts.

@boxofyellowboxofyellow left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Looks good to me.

@philip-gai
philip-gai merged commit ffdc20e into mainJul 13, 2026
25 of 27 checks passed
@philip-gai
philip-gai deleted the philip-gai/cache-read-denied-warning branch July 13, 2026 15:03
philip-gai added a commit that referenced this pull request Jul 15, 2026
#2451)
Backport of the read-denied and ACTIONS_CACHE_MODE cache-mode gating from
the ESM v6.2.0 line (#2447) to the CommonJS v5 line, released as 5.2.0.
Mirrors the earlier write-denied backport (#2435, 5.1.0).
- Detect the `cache read denied:` prefix on download failures (v2 twirp
path and v1 `_apis/artifactcache` path) and surface it as a core.warning
without failing the run.
- Honor ACTIONS_CACHE_MODE: skip restore when the effective cache-mode does
not permit reads (none, write-only) and skip save when it does not permit
writes (none, read), logging a single non-fatal core.info line. Unset or
unrecognized modes are unchanged.
- Add read-denied and cache-mode tests; bump to 5.2.0 with RELEASES entry.
Copilot-Session: e96deec1-716e-4e14-acdf-a230139420a2
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
zanieb added a commit to astral-sh/uv that referenced this pull request Aug 18, 2026
Trusted release actions must not accidentally restore a poisoned shared
GitHub cache. Set `ACTIONS_CACHE_MODE: none` in `release.yml` and each
reusable workflow it calls so clients that support the variable skip
cache reads and writes. Each reusable workflow needs its own setting
because workflow-level environment variables do not propagate to called
workflows. The shared binary-build workflow uses the same cache-disabled
setting when invoked by CI.
This is a cooperative client-side default, not token revocation or
server-enforced denial. Older or independent clients, or code calling
the cache API directly, can still access the service. The
`@actions/cache` library's [6.2.0
change](actions/toolkit#2447) skips operations
before contacting the cache backend; that is separate from its handling
of server-denied requests. The release graph uses `setup-uv@v10.0.1` and
`setup-python@v7.0.0`, whose pinned
[setup-uv](https://github.com/astral-sh/setup-uv/blob/20cfd1bf945f4377ade1205e4dbc17946fc9a30d/package-lock.json)
and
[setup-python](https://github.com/actions/setup-python/blob/5fda3b95a4ea91299a34e894583c3862153e4b97/package-lock.json)
dependencies include `@actions/cache@6.2.0`. Both honor this variable.
The separate `actions/cache@v6.1.0` action still bundles
`@actions/cache@6.1.0` and does not honor it; the release graph does not
invoke that action directly.
Stacked on astral-sh#761, which keeps the action-specific
opt-outs, removes the docs Rust cache, and explicitly disables maturin's
sccache integration. `release-prepare.yml`, same-run artifacts, and
Depot's own cache are unchanged.
Co-authored-by: Zanie Blue <contact@zanie.dev>
Sign up for freeto 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.

4 participants

@philip-gai@jasongin@boxofyellow
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

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

Merged
philip-gai merged 15 commits into
mainfrom
philip-gai/cache-read-denied-warning
Jul 13, 2026
Merged

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

Conversation

@philip-gai

@philip-gaiphilip-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

CopilotAI 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
FileDescription
packages/cache/src/internal/cacheHttpClient.tsSurfaces receiver error message only for cache read denied: so restore paths can reclassify it.
packages/cache/src/cache.tsAdds read-denied prefix + error type and handles read-denied restores as warning + cache miss for both v1/v2.
packages/cache/tests/restoreCache.test.tsAdds v1 tests asserting warning behavior and cache-miss semantics for read denial.
packages/cache/tests/restoreCacheV2.test.tsAdds v2 test asserting warning behavior and cache-miss semantics for wrapped read-denied errors.
packages/cache/tests/cacheHttpClient.test.tsAdds tests ensuring getCacheEntry only surfaces body for read-denied cases.
packages/cache/RELEASES.mdDocuments the 6.2.0 behavior change.
packages/cache/package.jsonBumps @actions/cache version to 6.2.0.
packages/cache/package-lock.jsonUpdates 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 threadpackages/cache/src/cache.ts Outdated
@philip-gaiphilip-gai changed the title Handle cache read error due to insufficient read permissionsfeat(cache): add cache-mode client behavior (read-denied warning + ACTIONS_CACHE_MODE skip)Jul 2, 2026

CopilotAI 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 threadpackages/cache/src/cache.ts Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@philip-gai
philip-gai marked this pull request as ready for review July 8, 2026 14:58
@philip-gai
philip-gai requested a review from a team as a code ownerJuly 8, 2026 14:58
jasongin
jasongin previously approved these changes Jul 9, 2026
Comment threadpackages/cache/src/internal/config.ts Outdated
jasongin
jasongin previously approved these changes Jul 10, 2026
Comment threadpackages/cache/__tests__/config.test.ts
Comment threadpackages/cache/__tests__/config.test.ts
Comment threadpackages/cache/__tests__/restoreCache.test.ts Outdated
Comment threadpackages/cache/__tests__/restoreCache.test.ts Outdated
Comment threadpackages/cache/__tests__/saveCache.test.ts Outdated
Comment threadpackages/cache/src/internal/cacheHttpClient.ts
Comment threadpackages/cache/src/cache.ts Outdated
Comment threadpackages/cache/src/cache.ts Outdated
Comment threadpackages/cache/src/cache.ts
…denied handling
Address PR review feedback:
- Merge the duplicate restore/save skip test.each blocks into single blocks parametrized over ACTIONS_CACHE_SERVICE_V2.
- Drop the redundant CacheReadDeniedError catch arms; the typed error is not an HttpClientError so it already falls through to a non-fatal warning.
- Clarify why read-denied classification happens both in getCacheEntry and cache.ts (dependency-free internal module cannot import the typed error).
Mirror the read-denied simplification on the save path. CacheWriteDeniedError
is not an HttpClientError and its name does not match the ReserveCacheError
arm, so it falls through to the same non-fatal warning. Logging behavior is
unchanged (warns, never fails the run) and the exported type is still thrown
internally for consumers and tests. Also refresh stale doc wording.
The two restoreCache tests exercised the identical warning + cache-miss path
now that read-denied is no longer reclassified in the catch, so merge them into
one. The read-denied prefix detection that actually branches on the message is
covered by getCacheEntry tests in cacheHttpClient.test.ts.

@boxofyellowboxofyellow left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Looks good to me.

@philip-gai
philip-gai merged commit ffdc20e into mainJul 13, 2026
25 of 27 checks passed
@philip-gai
philip-gai deleted the philip-gai/cache-read-denied-warning branch July 13, 2026 15:03
philip-gai added a commit that referenced this pull request Jul 15, 2026
#2451)
Backport of the read-denied and ACTIONS_CACHE_MODE cache-mode gating from
the ESM v6.2.0 line (#2447) to the CommonJS v5 line, released as 5.2.0.
Mirrors the earlier write-denied backport (#2435, 5.1.0).
- Detect the `cache read denied:` prefix on download failures (v2 twirp
path and v1 `_apis/artifactcache` path) and surface it as a core.warning
without failing the run.
- Honor ACTIONS_CACHE_MODE: skip restore when the effective cache-mode does
not permit reads (none, write-only) and skip save when it does not permit
writes (none, read), logging a single non-fatal core.info line. Unset or
unrecognized modes are unchanged.
- Add read-denied and cache-mode tests; bump to 5.2.0 with RELEASES entry.
Copilot-Session: e96deec1-716e-4e14-acdf-a230139420a2
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
zanieb added a commit to astral-sh/uv that referenced this pull request Aug 18, 2026
Trusted release actions must not accidentally restore a poisoned shared
GitHub cache. Set `ACTIONS_CACHE_MODE: none` in `release.yml` and each
reusable workflow it calls so clients that support the variable skip
cache reads and writes. Each reusable workflow needs its own setting
because workflow-level environment variables do not propagate to called
workflows. The shared binary-build workflow uses the same cache-disabled
setting when invoked by CI.
This is a cooperative client-side default, not token revocation or
server-enforced denial. Older or independent clients, or code calling
the cache API directly, can still access the service. The
`@actions/cache` library's [6.2.0
change](actions/toolkit#2447) skips operations
before contacting the cache backend; that is separate from its handling
of server-denied requests. The release graph uses `setup-uv@v10.0.1` and
`setup-python@v7.0.0`, whose pinned
[setup-uv](https://github.com/astral-sh/setup-uv/blob/20cfd1bf945f4377ade1205e4dbc17946fc9a30d/package-lock.json)
and
[setup-python](https://github.com/actions/setup-python/blob/5fda3b95a4ea91299a34e894583c3862153e4b97/package-lock.json)
dependencies include `@actions/cache@6.2.0`. Both honor this variable.
The separate `actions/cache@v6.1.0` action still bundles
`@actions/cache@6.1.0` and does not honor it; the release graph does not
invoke that action directly.
Stacked on astral-sh#761, which keeps the action-specific
opt-outs, removes the docs Rust cache, and explicitly disables maturin's
sccache integration. `release-prepare.yml`, same-run artifacts, and
Depot's own cache are unchanged.
Co-authored-by: Zanie Blue <contact@zanie.dev>
Sign up for freeto 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.

4 participants

@philip-gai@jasongin@boxofyellow
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

Merged
philip-gai merged 15 commits into
mainfrom
philip-gai/cache-read-denied-warning
Jul 13, 2026
Merged

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

Conversation

@philip-gai

@philip-gaiphilip-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

CopilotAI 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
FileDescription
packages/cache/src/internal/cacheHttpClient.tsSurfaces receiver error message only for cache read denied: so restore paths can reclassify it.
packages/cache/src/cache.tsAdds read-denied prefix + error type and handles read-denied restores as warning + cache miss for both v1/v2.
packages/cache/tests/restoreCache.test.tsAdds v1 tests asserting warning behavior and cache-miss semantics for read denial.
packages/cache/tests/restoreCacheV2.test.tsAdds v2 test asserting warning behavior and cache-miss semantics for wrapped read-denied errors.
packages/cache/tests/cacheHttpClient.test.tsAdds tests ensuring getCacheEntry only surfaces body for read-denied cases.
packages/cache/RELEASES.mdDocuments the 6.2.0 behavior change.
packages/cache/package.jsonBumps @actions/cache version to 6.2.0.
packages/cache/package-lock.jsonUpdates 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 threadpackages/cache/src/cache.ts Outdated
@philip-gaiphilip-gai changed the title Handle cache read error due to insufficient read permissionsfeat(cache): add cache-mode client behavior (read-denied warning + ACTIONS_CACHE_MODE skip)Jul 2, 2026

CopilotAI 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 threadpackages/cache/src/cache.ts Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@philip-gai
philip-gai marked this pull request as ready for review July 8, 2026 14:58
@philip-gai
philip-gai requested a review from a team as a code ownerJuly 8, 2026 14:58
jasongin
jasongin previously approved these changes Jul 9, 2026
Comment threadpackages/cache/src/internal/config.ts Outdated
jasongin
jasongin previously approved these changes Jul 10, 2026
Comment threadpackages/cache/__tests__/config.test.ts
Comment threadpackages/cache/__tests__/config.test.ts
Comment threadpackages/cache/__tests__/restoreCache.test.ts Outdated
Comment threadpackages/cache/__tests__/restoreCache.test.ts Outdated
Comment threadpackages/cache/__tests__/saveCache.test.ts Outdated
Comment threadpackages/cache/src/internal/cacheHttpClient.ts
Comment threadpackages/cache/src/cache.ts Outdated
Comment threadpackages/cache/src/cache.ts Outdated
Comment threadpackages/cache/src/cache.ts
…denied handling
Address PR review feedback:
- Merge the duplicate restore/save skip test.each blocks into single blocks parametrized over ACTIONS_CACHE_SERVICE_V2.
- Drop the redundant CacheReadDeniedError catch arms; the typed error is not an HttpClientError so it already falls through to a non-fatal warning.
- Clarify why read-denied classification happens both in getCacheEntry and cache.ts (dependency-free internal module cannot import the typed error).
Mirror the read-denied simplification on the save path. CacheWriteDeniedError
is not an HttpClientError and its name does not match the ReserveCacheError
arm, so it falls through to the same non-fatal warning. Logging behavior is
unchanged (warns, never fails the run) and the exported type is still thrown
internally for consumers and tests. Also refresh stale doc wording.
The two restoreCache tests exercised the identical warning + cache-miss path
now that read-denied is no longer reclassified in the catch, so merge them into
one. The read-denied prefix detection that actually branches on the message is
covered by getCacheEntry tests in cacheHttpClient.test.ts.

@boxofyellowboxofyellow left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Looks good to me.

@philip-gai
philip-gai merged commit ffdc20e into mainJul 13, 2026
25 of 27 checks passed
@philip-gai
philip-gai deleted the philip-gai/cache-read-denied-warning branch July 13, 2026 15:03
philip-gai added a commit that referenced this pull request Jul 15, 2026
#2451)
Backport of the read-denied and ACTIONS_CACHE_MODE cache-mode gating from
the ESM v6.2.0 line (#2447) to the CommonJS v5 line, released as 5.2.0.
Mirrors the earlier write-denied backport (#2435, 5.1.0).
- Detect the `cache read denied:` prefix on download failures (v2 twirp
path and v1 `_apis/artifactcache` path) and surface it as a core.warning
without failing the run.
- Honor ACTIONS_CACHE_MODE: skip restore when the effective cache-mode does
not permit reads (none, write-only) and skip save when it does not permit
writes (none, read), logging a single non-fatal core.info line. Unset or
unrecognized modes are unchanged.
- Add read-denied and cache-mode tests; bump to 5.2.0 with RELEASES entry.
Copilot-Session: e96deec1-716e-4e14-acdf-a230139420a2
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
zanieb added a commit to astral-sh/uv that referenced this pull request Aug 18, 2026
Trusted release actions must not accidentally restore a poisoned shared
GitHub cache. Set `ACTIONS_CACHE_MODE: none` in `release.yml` and each
reusable workflow it calls so clients that support the variable skip
cache reads and writes. Each reusable workflow needs its own setting
because workflow-level environment variables do not propagate to called
workflows. The shared binary-build workflow uses the same cache-disabled
setting when invoked by CI.
This is a cooperative client-side default, not token revocation or
server-enforced denial. Older or independent clients, or code calling
the cache API directly, can still access the service. The
`@actions/cache` library's [6.2.0
change](actions/toolkit#2447) skips operations
before contacting the cache backend; that is separate from its handling
of server-denied requests. The release graph uses `setup-uv@v10.0.1` and
`setup-python@v7.0.0`, whose pinned
[setup-uv](https://github.com/astral-sh/setup-uv/blob/20cfd1bf945f4377ade1205e4dbc17946fc9a30d/package-lock.json)
and
[setup-python](https://github.com/actions/setup-python/blob/5fda3b95a4ea91299a34e894583c3862153e4b97/package-lock.json)
dependencies include `@actions/cache@6.2.0`. Both honor this variable.
The separate `actions/cache@v6.1.0` action still bundles
`@actions/cache@6.1.0` and does not honor it; the release graph does not
invoke that action directly.
Stacked on astral-sh#761, which keeps the action-specific
opt-outs, removes the docs Rust cache, and explicitly disables maturin's
sccache integration. `release-prepare.yml`, same-run artifacts, and
Depot's own cache are unchanged.
Co-authored-by: Zanie Blue <contact@zanie.dev>
Sign up for freeto 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.

4 participants

@philip-gai@jasongin@boxofyellow
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

Merged
philip-gai merged 15 commits into
mainfrom
philip-gai/cache-read-denied-warning
Jul 13, 2026
Merged

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

Conversation

@philip-gai

@philip-gaiphilip-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

CopilotAI 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
FileDescription
packages/cache/src/internal/cacheHttpClient.tsSurfaces receiver error message only for cache read denied: so restore paths can reclassify it.
packages/cache/src/cache.tsAdds read-denied prefix + error type and handles read-denied restores as warning + cache miss for both v1/v2.
packages/cache/tests/restoreCache.test.tsAdds v1 tests asserting warning behavior and cache-miss semantics for read denial.
packages/cache/tests/restoreCacheV2.test.tsAdds v2 test asserting warning behavior and cache-miss semantics for wrapped read-denied errors.
packages/cache/tests/cacheHttpClient.test.tsAdds tests ensuring getCacheEntry only surfaces body for read-denied cases.
packages/cache/RELEASES.mdDocuments the 6.2.0 behavior change.
packages/cache/package.jsonBumps @actions/cache version to 6.2.0.
packages/cache/package-lock.jsonUpdates 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 threadpackages/cache/src/cache.ts Outdated
@philip-gaiphilip-gai changed the title Handle cache read error due to insufficient read permissionsfeat(cache): add cache-mode client behavior (read-denied warning + ACTIONS_CACHE_MODE skip)Jul 2, 2026

CopilotAI 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 threadpackages/cache/src/cache.ts Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@philip-gai
philip-gai marked this pull request as ready for review July 8, 2026 14:58
@philip-gai
philip-gai requested a review from a team as a code ownerJuly 8, 2026 14:58
jasongin
jasongin previously approved these changes Jul 9, 2026
Comment threadpackages/cache/src/internal/config.ts Outdated
jasongin
jasongin previously approved these changes Jul 10, 2026
Comment threadpackages/cache/__tests__/config.test.ts
Comment threadpackages/cache/__tests__/config.test.ts
Comment threadpackages/cache/__tests__/restoreCache.test.ts Outdated
Comment threadpackages/cache/__tests__/restoreCache.test.ts Outdated
Comment threadpackages/cache/__tests__/saveCache.test.ts Outdated
Comment threadpackages/cache/src/internal/cacheHttpClient.ts
Comment threadpackages/cache/src/cache.ts Outdated
Comment threadpackages/cache/src/cache.ts Outdated
Comment threadpackages/cache/src/cache.ts
…denied handling
Address PR review feedback:
- Merge the duplicate restore/save skip test.each blocks into single blocks parametrized over ACTIONS_CACHE_SERVICE_V2.
- Drop the redundant CacheReadDeniedError catch arms; the typed error is not an HttpClientError so it already falls through to a non-fatal warning.
- Clarify why read-denied classification happens both in getCacheEntry and cache.ts (dependency-free internal module cannot import the typed error).
Mirror the read-denied simplification on the save path. CacheWriteDeniedError
is not an HttpClientError and its name does not match the ReserveCacheError
arm, so it falls through to the same non-fatal warning. Logging behavior is
unchanged (warns, never fails the run) and the exported type is still thrown
internally for consumers and tests. Also refresh stale doc wording.
The two restoreCache tests exercised the identical warning + cache-miss path
now that read-denied is no longer reclassified in the catch, so merge them into
one. The read-denied prefix detection that actually branches on the message is
covered by getCacheEntry tests in cacheHttpClient.test.ts.

@boxofyellowboxofyellow left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Looks good to me.

@philip-gai
philip-gai merged commit ffdc20e into mainJul 13, 2026
25 of 27 checks passed
@philip-gai
philip-gai deleted the philip-gai/cache-read-denied-warning branch July 13, 2026 15:03
philip-gai added a commit that referenced this pull request Jul 15, 2026
#2451)
Backport of the read-denied and ACTIONS_CACHE_MODE cache-mode gating from
the ESM v6.2.0 line (#2447) to the CommonJS v5 line, released as 5.2.0.
Mirrors the earlier write-denied backport (#2435, 5.1.0).
- Detect the `cache read denied:` prefix on download failures (v2 twirp
path and v1 `_apis/artifactcache` path) and surface it as a core.warning
without failing the run.
- Honor ACTIONS_CACHE_MODE: skip restore when the effective cache-mode does
not permit reads (none, write-only) and skip save when it does not permit
writes (none, read), logging a single non-fatal core.info line. Unset or
unrecognized modes are unchanged.
- Add read-denied and cache-mode tests; bump to 5.2.0 with RELEASES entry.
Copilot-Session: e96deec1-716e-4e14-acdf-a230139420a2
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
zanieb added a commit to astral-sh/uv that referenced this pull request Aug 18, 2026
Trusted release actions must not accidentally restore a poisoned shared
GitHub cache. Set `ACTIONS_CACHE_MODE: none` in `release.yml` and each
reusable workflow it calls so clients that support the variable skip
cache reads and writes. Each reusable workflow needs its own setting
because workflow-level environment variables do not propagate to called
workflows. The shared binary-build workflow uses the same cache-disabled
setting when invoked by CI.
This is a cooperative client-side default, not token revocation or
server-enforced denial. Older or independent clients, or code calling
the cache API directly, can still access the service. The
`@actions/cache` library's [6.2.0
change](actions/toolkit#2447) skips operations
before contacting the cache backend; that is separate from its handling
of server-denied requests. The release graph uses `setup-uv@v10.0.1` and
`setup-python@v7.0.0`, whose pinned
[setup-uv](https://github.com/astral-sh/setup-uv/blob/20cfd1bf945f4377ade1205e4dbc17946fc9a30d/package-lock.json)
and
[setup-python](https://github.com/actions/setup-python/blob/5fda3b95a4ea91299a34e894583c3862153e4b97/package-lock.json)
dependencies include `@actions/cache@6.2.0`. Both honor this variable.
The separate `actions/cache@v6.1.0` action still bundles
`@actions/cache@6.1.0` and does not honor it; the release graph does not
invoke that action directly.
Stacked on astral-sh#761, which keeps the action-specific
opt-outs, removes the docs Rust cache, and explicitly disables maturin's
sccache integration. `release-prepare.yml`, same-run artifacts, and
Depot's own cache are unchanged.
Co-authored-by: Zanie Blue <contact@zanie.dev>
Sign up for freeto 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.

4 participants

@philip-gai@jasongin@boxofyellow
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

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

Merged
philip-gai merged 15 commits into
mainfrom
philip-gai/cache-read-denied-warning
Jul 13, 2026
Merged

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

Conversation

@philip-gai

@philip-gaiphilip-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

CopilotAI 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
FileDescription
packages/cache/src/internal/cacheHttpClient.tsSurfaces receiver error message only for cache read denied: so restore paths can reclassify it.
packages/cache/src/cache.tsAdds read-denied prefix + error type and handles read-denied restores as warning + cache miss for both v1/v2.
packages/cache/tests/restoreCache.test.tsAdds v1 tests asserting warning behavior and cache-miss semantics for read denial.
packages/cache/tests/restoreCacheV2.test.tsAdds v2 test asserting warning behavior and cache-miss semantics for wrapped read-denied errors.
packages/cache/tests/cacheHttpClient.test.tsAdds tests ensuring getCacheEntry only surfaces body for read-denied cases.
packages/cache/RELEASES.mdDocuments the 6.2.0 behavior change.
packages/cache/package.jsonBumps @actions/cache version to 6.2.0.
packages/cache/package-lock.jsonUpdates 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 threadpackages/cache/src/cache.ts Outdated
@philip-gaiphilip-gai changed the title Handle cache read error due to insufficient read permissionsfeat(cache): add cache-mode client behavior (read-denied warning + ACTIONS_CACHE_MODE skip)Jul 2, 2026

CopilotAI 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 threadpackages/cache/src/cache.ts Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@philip-gai
philip-gai marked this pull request as ready for review July 8, 2026 14:58
@philip-gai
philip-gai requested a review from a team as a code ownerJuly 8, 2026 14:58
jasongin
jasongin previously approved these changes Jul 9, 2026
Comment threadpackages/cache/src/internal/config.ts Outdated
jasongin
jasongin previously approved these changes Jul 10, 2026
Comment threadpackages/cache/__tests__/config.test.ts
Comment threadpackages/cache/__tests__/config.test.ts
Comment threadpackages/cache/__tests__/restoreCache.test.ts Outdated
Comment threadpackages/cache/__tests__/restoreCache.test.ts Outdated
Comment threadpackages/cache/__tests__/saveCache.test.ts Outdated
Comment threadpackages/cache/src/internal/cacheHttpClient.ts
Comment threadpackages/cache/src/cache.ts Outdated
Comment threadpackages/cache/src/cache.ts Outdated
Comment threadpackages/cache/src/cache.ts
…denied handling
Address PR review feedback:
- Merge the duplicate restore/save skip test.each blocks into single blocks parametrized over ACTIONS_CACHE_SERVICE_V2.
- Drop the redundant CacheReadDeniedError catch arms; the typed error is not an HttpClientError so it already falls through to a non-fatal warning.
- Clarify why read-denied classification happens both in getCacheEntry and cache.ts (dependency-free internal module cannot import the typed error).
Mirror the read-denied simplification on the save path. CacheWriteDeniedError
is not an HttpClientError and its name does not match the ReserveCacheError
arm, so it falls through to the same non-fatal warning. Logging behavior is
unchanged (warns, never fails the run) and the exported type is still thrown
internally for consumers and tests. Also refresh stale doc wording.
The two restoreCache tests exercised the identical warning + cache-miss path
now that read-denied is no longer reclassified in the catch, so merge them into
one. The read-denied prefix detection that actually branches on the message is
covered by getCacheEntry tests in cacheHttpClient.test.ts.

@boxofyellowboxofyellow left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Looks good to me.

@philip-gai
philip-gai merged commit ffdc20e into mainJul 13, 2026
25 of 27 checks passed
@philip-gai
philip-gai deleted the philip-gai/cache-read-denied-warning branch July 13, 2026 15:03
philip-gai added a commit that referenced this pull request Jul 15, 2026
#2451)
Backport of the read-denied and ACTIONS_CACHE_MODE cache-mode gating from
the ESM v6.2.0 line (#2447) to the CommonJS v5 line, released as 5.2.0.
Mirrors the earlier write-denied backport (#2435, 5.1.0).
- Detect the `cache read denied:` prefix on download failures (v2 twirp
path and v1 `_apis/artifactcache` path) and surface it as a core.warning
without failing the run.
- Honor ACTIONS_CACHE_MODE: skip restore when the effective cache-mode does
not permit reads (none, write-only) and skip save when it does not permit
writes (none, read), logging a single non-fatal core.info line. Unset or
unrecognized modes are unchanged.
- Add read-denied and cache-mode tests; bump to 5.2.0 with RELEASES entry.
Copilot-Session: e96deec1-716e-4e14-acdf-a230139420a2
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
zanieb added a commit to astral-sh/uv that referenced this pull request Aug 18, 2026
Trusted release actions must not accidentally restore a poisoned shared
GitHub cache. Set `ACTIONS_CACHE_MODE: none` in `release.yml` and each
reusable workflow it calls so clients that support the variable skip
cache reads and writes. Each reusable workflow needs its own setting
because workflow-level environment variables do not propagate to called
workflows. The shared binary-build workflow uses the same cache-disabled
setting when invoked by CI.
This is a cooperative client-side default, not token revocation or
server-enforced denial. Older or independent clients, or code calling
the cache API directly, can still access the service. The
`@actions/cache` library's [6.2.0
change](actions/toolkit#2447) skips operations
before contacting the cache backend; that is separate from its handling
of server-denied requests. The release graph uses `setup-uv@v10.0.1` and
`setup-python@v7.0.0`, whose pinned
[setup-uv](https://github.com/astral-sh/setup-uv/blob/20cfd1bf945f4377ade1205e4dbc17946fc9a30d/package-lock.json)
and
[setup-python](https://github.com/actions/setup-python/blob/5fda3b95a4ea91299a34e894583c3862153e4b97/package-lock.json)
dependencies include `@actions/cache@6.2.0`. Both honor this variable.
The separate `actions/cache@v6.1.0` action still bundles
`@actions/cache@6.1.0` and does not honor it; the release graph does not
invoke that action directly.
Stacked on astral-sh#761, which keeps the action-specific
opt-outs, removes the docs Rust cache, and explicitly disables maturin's
sccache integration. `release-prepare.yml`, same-run artifacts, and
Depot's own cache are unchanged.
Co-authored-by: Zanie Blue <contact@zanie.dev>
Sign up for freeto 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.

4 participants

@philip-gai@jasongin@boxofyellow