Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion 2 .licenses/npm/@actions/cache.dep.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 11 additions & 3 deletions 14 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ If you do not upgrade, all workflow runs using any of the deprecated [actions/ca

Upgrading to the recommended versions will not break your workflows.

> **Additionally, if you are managing your own GitHub runners, you must update your runner version to `2.231.0` or newer to ensure compatibility with the new cache service.**
> **Additionally, if you are managing your own GitHub runners, you must update your runner version to `2.231.0` or newer to ensure compatibility with the new cache service.**
> Failure to update both the action version and your runner version may result in workflow failures after the migration date.

Read more about the change & access the migration guide: [reference to the announcement](https://github.com/actions/cache/discussions/1510).
Expand Down Expand Up @@ -109,6 +109,14 @@ The cache is scoped to the key, [version](#cache-version), and branch. The defau

See [Matching a cache key](https://help.github.com/en/actions/configuring-and-managing-workflows/caching-dependencies-to-speed-up-workflows#matching-a-cache-key) for more info.

### Read-only access

Some workflow runs only have read-only access to the cache. A common case is a workflow triggered by a pull request from a fork: such runs can **restore** existing caches but may not be permitted to **save** new ones.

When the cache token is read-only, the save step does not fail the job. Instead, `@actions/cache` reports the denial once as a warning (for example, `Failed to save: ... cache write denied: ...`) and the step completes successfully without writing a cache entry. Restores in the same run continue to work as usual.

> **Note** This applies to the action's normal save path as well as the standalone [Save action](./save/README.md). If you intentionally want a restore-only setup, see [Make cache read only / Reuse cache from centralized job](./caching-strategies.md#make-cache-read-only--reuse-cache-from-centralized-job).

### Example cache workflow

#### Restoring and saving cache using a single action
Expand Down Expand Up @@ -351,7 +359,7 @@ Please note that Windows environment variables (like `%LocalAppData%`) will NOT

## Note

Thank you for your interest in this GitHub repo, however, right now we are not taking contributions.
Thank you for your interest in this GitHub repo, however, right now we are not taking contributions.

We continue to focus our resources on strategic areas that help our customers be successful while making developers' lives easier. While GitHub Actions remains a key part of this vision, we are allocating resources towards other areas of Actions and are not taking contributions to this repository at this time. The GitHub public roadmap is the best place to follow along for any updates on features we’re working on and what stage they’re in.

Expand All @@ -369,4 +377,4 @@ You are welcome to still raise bugs in this repo.

## License

The scripts and documentation in this project are released under the [MIT License](LICENSE)
The scripts and documentation in this project are released under the [MIT License](LICENSE)
9 changes: 7 additions & 2 deletions 9 RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## How to prepare a release

> [!NOTE]
> [!NOTE]
> Relevant for maintainers with write access only.

1. Switch to a new branch from `main`.
Expand All @@ -21,10 +21,15 @@
1. Publish the release.
1. Navigate to https://github.com/actions/cache/actions/workflows/release-new-action-version.yml
1. There should be a workflow run queued with the same version number.
1. Approve the run to publish the new version and update the major tags for this action.
1. Approve the run to publish the new version and update the major tags for this action.

## Changelog

### 5.1.0

- Bump `@actions/cache` to v5.1.0 to pick up [actions/toolkit#2435 Handle cache write error due to read-only token](https://github.com/actions/toolkit/pull/2435)
- Switch redundant "Cache save failed" warning to debug log in save-only

### 5.0.4

- Bump `minimatch` to v3.1.5 (fixes ReDoS via globstar patterns)
Expand Down
12 changes: 9 additions & 3 deletions 12 __tests__/saveOnly.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,10 @@ test("save with valid inputs uploads a cache", async () => {
expect(failedMock).toHaveBeenCalledTimes(0);
});

test("save failing logs the warning message", async () => {
test("save failing logs the debug message", async () => {
const debugMock = jest.spyOn(core, "debug");
const warningMock = jest.spyOn(core, "warning");
const failedMock = jest.spyOn(core, "setFailed");

const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";

Expand All @@ -115,6 +117,9 @@ test("save failing logs the warning message", async () => {
testUtils.setInput(Inputs.Path, inputPath);
testUtils.setInput(Inputs.UploadChunkSize, "4000000");

// A read-only / write-denied save surfaces to the action as saveCache resolving
// to -1; the toolkit has already logged the underlying reason. The action
// must not fail the job or emit its own warning.
const cacheId = -1;
const saveCacheMock = jest
.spyOn(cache, "saveCache")
Expand All @@ -134,6 +139,7 @@ test("save failing logs the warning message", async () => {
false
);

expect(warningMock).toHaveBeenCalledTimes(1);
expect(warningMock).toHaveBeenCalledWith("Cache save failed.");
expect(debugMock).toHaveBeenCalledWith("Cache was not saved.");
expect(warningMock).not.toHaveBeenCalled();
expect(failedMock).not.toHaveBeenCalled();
});
47 changes: 43 additions & 4 deletions 47 dist/restore-only/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.FinalizeCacheError = exports.ReserveCacheError = exports.ValidationError = void 0;
exports.FinalizeCacheError = exports.CacheWriteDeniedError = exports.CACHE_WRITE_DENIED_PREFIX = exports.ReserveCacheError = exports.ValidationError = void 0;
exports.isFeatureAvailable = isFeatureAvailable;
exports.restoreCache = restoreCache;
exports.saveCache = saveCache;
Expand Down Expand Up @@ -77,6 +77,26 @@ class ReserveCacheError extends Error {
}
}
exports.ReserveCacheError = ReserveCacheError;
/**
* Stable prefix used by the cache receiver to signal that the token has
* no writable scopes (read-only cache policy). Consumers can match on
* this prefix to distinguish policy denials from ordinary contention.
*/
exports.CACHE_WRITE_DENIED_PREFIX = 'cache write denied:';
/**
* Extends ReserveCacheError for source-compatibility: existing
* `instanceof ReserveCacheError` checks and `typedError.name ===
* ReserveCacheError.name` paths keep working, while consumers that want to
* distinguish a policy denial can check for CacheWriteDeniedError.name.
*/
class CacheWriteDeniedError extends ReserveCacheError {
constructor(message) {
super(message);
this.name = 'CacheWriteDeniedError';
Object.setPrototypeOf(this, CacheWriteDeniedError.prototype);
}
}
exports.CacheWriteDeniedError = CacheWriteDeniedError;
class FinalizeCacheError extends Error {
constructor(message) {
super(message);
Expand Down Expand Up @@ -387,7 +407,11 @@ function saveCacheV1(paths_1, key_1, options_1) {
throw new Error((_d = (_c = reserveCacheResponse === null || reserveCacheResponse === void 0 ? void 0 : reserveCacheResponse.error) === null || _c === void 0 ? void 0 : _c.message) !== null && _d !== void 0 ? _d : `Cache size of ~${Math.round(archiveFileSize / (1024 * 1024))} MB (${archiveFileSize} B) is over the data cap limit, not saving cache.`);
}
else {
throw new ReserveCacheError(`Unable to reserve cache with key ${key}, another job may be creating this cache. More details: ${(_e = reserveCacheResponse === null || reserveCacheResponse === void 0 ? void 0 : reserveCacheResponse.error) === null || _e === void 0 ? void 0 : _e.message}`);
const detailMessage = (_e = reserveCacheResponse === null || reserveCacheResponse === void 0 ? void 0 : reserveCacheResponse.error) === null || _e === void 0 ? void 0 : _e.message;
if (detailMessage === null || detailMessage === void 0 ? void 0 : detailMessage.startsWith(exports.CACHE_WRITE_DENIED_PREFIX)) {
throw new CacheWriteDeniedError(`Unable to reserve cache with key ${key}. More details: ${detailMessage}`);
}
throw new ReserveCacheError(`Unable to reserve cache with key ${key}, another job may be creating this cache. More details: ${detailMessage}`);
}
core.debug(`Saving Cache (ID: ${cacheId})`);
yield cacheHttpClient.saveCache(cacheId, archivePath, '', options);
Expand All @@ -397,6 +421,9 @@ function saveCacheV1(paths_1, key_1, options_1) {
if (typedError.name === ValidationError.name) {
throw error;
}
else if (typedError.name === CacheWriteDeniedError.name) {
core.warning(`Failed to save: ${typedError.message}`);
}
else if (typedError.name === ReserveCacheError.name) {
core.info(`Failed to save: ${typedError.message}`);
}
Expand Down Expand Up @@ -435,6 +462,7 @@ function saveCacheV1(paths_1, key_1, options_1) {
*/
function saveCacheV2(paths_1, key_1, options_1) {
return __awaiter(this, arguments, void 0, function* (paths, key, options, enableCrossOsArchive = false) {
var _a;
// Override UploadOptions to force the use of Azure
// ...options goes first because we want to override the default values
// set in UploadOptions with these specific figures
Expand Down Expand Up @@ -470,7 +498,11 @@ function saveCacheV2(paths_1, key_1, options_1) {
try {
const response = yield twirpClient.CreateCacheEntry(request);
if (!response.ok) {
if (response.message) {
// Skip the redundant inner warning when the receiver signalled a
// policy denial: the outer catch arm below will log a single
// customer-facing warning.
if (response.message &&
!response.message.startsWith(exports.CACHE_WRITE_DENIED_PREFIX)) {
core.warning(`Cache reservation failed: ${response.message}`);
}
throw new Error(response.message || 'Response was not ok');
Expand All @@ -479,6 +511,10 @@ function saveCacheV2(paths_1, key_1, options_1) {
}
catch (error) {
core.debug(`Failed to reserve cache: ${error}`);
const errorMessage = (_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : '';
if (errorMessage.startsWith(exports.CACHE_WRITE_DENIED_PREFIX)) {
throw new CacheWriteDeniedError(`Unable to reserve cache with key ${key}. More details: ${errorMessage}`);
}
throw new ReserveCacheError(`Unable to reserve cache with key ${key}, another job may be creating this cache.`);
}
core.debug(`Attempting to upload cache located at: ${archivePath}`);
Expand All @@ -503,6 +539,9 @@ function saveCacheV2(paths_1, key_1, options_1) {
if (typedError.name === ValidationError.name) {
throw error;
}
else if (typedError.name === CacheWriteDeniedError.name) {
core.warning(`Failed to save: ${typedError.message}`);
}
else if (typedError.name === ReserveCacheError.name) {
core.info(`Failed to save: ${typedError.message}`);
}
Expand Down Expand Up @@ -87527,7 +87566,7 @@ function randomUUID() {
/***/ ((module) => {

"use strict";
module.exports = /*#__PURE__*/JSON.parse('{"name":"@actions/cache","version":"5.0.5","preview":true,"description":"Actions cache lib","keywords":["github","actions","cache"],"homepage":"https://github.com/actions/toolkit/tree/main/packages/cache","license":"MIT","main":"lib/cache.js","types":"lib/cache.d.ts","directories":{"lib":"lib","test":"__tests__"},"files":["lib","!.DS_Store"],"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/actions/toolkit.git","directory":"packages/cache"},"scripts":{"audit-moderate":"npm install && npm audit --json --audit-level=moderate > audit.json","test":"echo \\"Error: run tests from root\\" && exit 1","tsc":"tsc"},"bugs":{"url":"https://github.com/actions/toolkit/issues"},"dependencies":{"@actions/core":"^2.0.0","@actions/exec":"^2.0.0","@actions/glob":"^0.5.1","@protobuf-ts/runtime-rpc":"^2.11.1","@actions/http-client":"^3.0.2","@actions/io":"^2.0.0","@azure/abort-controller":"^1.1.0","@azure/core-rest-pipeline":"^1.22.0","@azure/storage-blob":"^12.29.1","semver":"^6.3.1"},"devDependencies":{"@types/node":"^24.1.0","@types/semver":"^6.0.0","@protobuf-ts/plugin":"^2.9.4","typescript":"^5.2.2"},"overrides":{"uri-js":"npm:uri-js-replace@^1.0.1","node-fetch":"^3.3.2"}}');
module.exports = /*#__PURE__*/JSON.parse('{"name":"@actions/cache","version":"5.1.0","preview":true,"description":"Actions cache lib","keywords":["github","actions","cache"],"homepage":"https://github.com/actions/toolkit/tree/main/packages/cache","license":"MIT","main":"lib/cache.js","types":"lib/cache.d.ts","directories":{"lib":"lib","test":"__tests__"},"files":["lib","!.DS_Store"],"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/actions/toolkit.git","directory":"packages/cache"},"scripts":{"audit-moderate":"npm install && npm audit --json --audit-level=moderate > audit.json","test":"echo \\"Error: run tests from root\\" && exit 1","tsc":"tsc"},"bugs":{"url":"https://github.com/actions/toolkit/issues"},"dependencies":{"@actions/core":"^2.0.0","@actions/exec":"^2.0.0","@actions/glob":"^0.5.1","@protobuf-ts/runtime-rpc":"^2.11.1","@actions/http-client":"^3.0.2","@actions/io":"^2.0.0","@azure/abort-controller":"^1.1.0","@azure/core-rest-pipeline":"^1.22.0","@azure/storage-blob":"^12.29.1","semver":"^6.3.1"},"devDependencies":{"@types/node":"^24.1.0","@types/semver":"^6.0.0","@protobuf-ts/plugin":"^2.9.4","typescript":"^5.2.2"},"overrides":{"uri-js":"npm:uri-js-replace@^1.0.1","node-fetch":"^3.3.2"}}');

/***/ })

Expand Down
Loading
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.