From bd12a7d8d1fc3c55de6f028ba2cca01cc3e28bac Mon Sep 17 00:00:00 2001 From: Jackie Li Date: Wed, 11 Mar 2026 16:48:26 +0000 Subject: [PATCH 1/3] feat: add cache-matched-key output --- README.md | 21 +++++++++++++++++++++ action.yml | 2 ++ dist/restore/index.js | 7 +++++++ dist/save/index.js | 4 ++++ dist/saveOnly/index.js | 4 ++++ restore/action.yml | 2 ++ src/restore.ts | 4 ++++ src/utils.ts | 4 ++++ 8 files changed, 48 insertions(+) diff --git a/README.md b/README.md index 3f1fbbb..36b7219 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,27 @@ To check if cache hits and size is not zero without downloading: `restore-keys` works similar to how github's `@actions/cache@v2` works: It search each item in `restore-keys` as prefix in object names and use the latest one +To restore from the cache using a `restore-key` prefix if the `key` restore fails: + +```yaml + - uses: tespkg/actions-cache/restore@v1 + with: + accessKey: "Q3AM3UQ867SPQQA43P2F" # required + secretKey: "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG" # required + bucket: actions-cache # required + # actions/cache compatible properties: https://github.com/actions/cache + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + ${{ runner.os }}- + path: | + node_modules +``` + +If a match is found using one of the `restore-keys` options, then `cache-hit` will be FALSE but the +`cache-matched-key` output will be set to the key that matched. See the +[actions/cache](https://github.com/actions/cache/blob/main/restore/README.md#outputs) notes. + ## Amazon S3 permissions When using this with Amazon S3, the following permissions are necessary: diff --git a/action.yml b/action.yml index d22c866..f614151 100644 --- a/action.yml +++ b/action.yml @@ -65,6 +65,8 @@ outputs: description: "A boolean value to indicate an exact match was found for the primary key" cache-size: description: "A integer value denoting the size of the cache object found (measured in bytes)" + cache-matched-key: + description: "The key of the cache object found, if any" runs: using: node20 main: "dist/restore/index.js" diff --git a/dist/restore/index.js b/dist/restore/index.js index 5f72acb..53c0f70 100644 --- a/dist/restore/index.js +++ b/dist/restore/index.js @@ -86081,6 +86081,7 @@ function restoreCache() { const cacheHit = matchingKey === key; (0, utils_1.setCacheHitOutput)(cacheHit); (0, utils_1.setCacheSizeOutput)(obj.size); + (0, utils_1.setCacheMatchedKeyOutput)(matchingKey); if (lookupOnly) { if (cacheHit && obj.size > 0) { core.info(`Cache Hit. NOT Downloading cache from s3 because lookup-only is set. bucket: ${bucket}, object: ${obj.name}`); @@ -86103,6 +86104,7 @@ function restoreCache() { catch (e) { core.info("Restore s3 cache failed: " + e.message); (0, utils_1.setCacheHitOutput)(false); + (0, utils_1.setCacheMatchedKeyOutput)(""); if (useFallback) { if ((0, utils_1.isGhes)()) { core.warning("Cache fallback is not supported on Github Enterpise."); @@ -86112,6 +86114,7 @@ function restoreCache() { const fallbackMatchingKey = yield cache.restoreCache(paths, key, restoreKeys); if (fallbackMatchingKey) { (0, utils_1.setCacheHitOutput)(fallbackMatchingKey === key); + (0, utils_1.setCacheMatchedKeyOutput)(fallbackMatchingKey); core.info("Fallback cache restored successfully"); } else { @@ -86212,6 +86215,7 @@ exports.getInputAsInt = getInputAsInt; exports.formatSize = formatSize; exports.setCacheHitOutput = setCacheHitOutput; exports.setCacheSizeOutput = setCacheSizeOutput; +exports.setCacheMatchedKeyOutput = setCacheMatchedKeyOutput; exports.findObject = findObject; exports.listObjects = listObjects; exports.saveMatchedKey = saveMatchedKey; @@ -86296,6 +86300,9 @@ function setCacheHitOutput(isCacheHit) { function setCacheSizeOutput(cacheSize) { core.setOutput("cache-size", cacheSize.toString()); } +function setCacheMatchedKeyOutput(cacheMatchedKey) { + core.setOutput("cache-matched-key", cacheMatchedKey); +} function findObject(mc, bucket, key, restoreKeys, compressionMethod) { return __awaiter(this, void 0, void 0, function* () { core.debug("Key: " + JSON.stringify(key)); diff --git a/dist/save/index.js b/dist/save/index.js index 7af964c..ad6560b 100644 --- a/dist/save/index.js +++ b/dist/save/index.js @@ -86127,6 +86127,7 @@ exports.getInputAsInt = getInputAsInt; exports.formatSize = formatSize; exports.setCacheHitOutput = setCacheHitOutput; exports.setCacheSizeOutput = setCacheSizeOutput; +exports.setCacheMatchedKeyOutput = setCacheMatchedKeyOutput; exports.findObject = findObject; exports.listObjects = listObjects; exports.saveMatchedKey = saveMatchedKey; @@ -86211,6 +86212,9 @@ function setCacheHitOutput(isCacheHit) { function setCacheSizeOutput(cacheSize) { core.setOutput("cache-size", cacheSize.toString()); } +function setCacheMatchedKeyOutput(cacheMatchedKey) { + core.setOutput("cache-matched-key", cacheMatchedKey); +} function findObject(mc, bucket, key, restoreKeys, compressionMethod) { return __awaiter(this, void 0, void 0, function* () { core.debug("Key: " + JSON.stringify(key)); diff --git a/dist/saveOnly/index.js b/dist/saveOnly/index.js index d6edfed..05cff6d 100644 --- a/dist/saveOnly/index.js +++ b/dist/saveOnly/index.js @@ -86127,6 +86127,7 @@ exports.getInputAsInt = getInputAsInt; exports.formatSize = formatSize; exports.setCacheHitOutput = setCacheHitOutput; exports.setCacheSizeOutput = setCacheSizeOutput; +exports.setCacheMatchedKeyOutput = setCacheMatchedKeyOutput; exports.findObject = findObject; exports.listObjects = listObjects; exports.saveMatchedKey = saveMatchedKey; @@ -86211,6 +86212,9 @@ function setCacheHitOutput(isCacheHit) { function setCacheSizeOutput(cacheSize) { core.setOutput("cache-size", cacheSize.toString()); } +function setCacheMatchedKeyOutput(cacheMatchedKey) { + core.setOutput("cache-matched-key", cacheMatchedKey); +} function findObject(mc, bucket, key, restoreKeys, compressionMethod) { return __awaiter(this, void 0, void 0, function* () { core.debug("Key: " + JSON.stringify(key)); diff --git a/restore/action.yml b/restore/action.yml index 433904b..7206c3a 100644 --- a/restore/action.yml +++ b/restore/action.yml @@ -65,6 +65,8 @@ outputs: description: "A boolean value to indicate an exact match was found for the primary key" cache-size: description: "A integer value denoting the size of the cache object found (measured in bytes)" + cache-matched-key: + description: "The key of the cache object found, if any" runs: using: node20 main: "../dist/restore/index.js" diff --git a/src/restore.ts b/src/restore.ts index b870216..474c085 100644 --- a/src/restore.ts +++ b/src/restore.ts @@ -12,6 +12,7 @@ import { isGhes, newMinio, setCacheHitOutput, + setCacheMatchedKeyOutput, setCacheSizeOutput, saveMatchedKey, getInput, @@ -67,6 +68,7 @@ async function restoreCache() { const cacheHit = matchingKey === key; setCacheHitOutput(cacheHit); setCacheSizeOutput(obj.size); + setCacheMatchedKeyOutput(matchingKey); if (lookupOnly) { if (cacheHit && obj.size > 0) { core.info( @@ -95,6 +97,7 @@ async function restoreCache() { } catch (e) { core.info("Restore s3 cache failed: " + e.message); setCacheHitOutput(false); + setCacheMatchedKeyOutput(""); if (useFallback) { if (isGhes()) { core.warning("Cache fallback is not supported on Github Enterpise."); @@ -107,6 +110,7 @@ async function restoreCache() { ); if (fallbackMatchingKey) { setCacheHitOutput(fallbackMatchingKey === key); + setCacheMatchedKeyOutput(fallbackMatchingKey); core.info("Fallback cache restored successfully"); } else { core.info("Fallback cache restore failed"); diff --git a/src/utils.ts b/src/utils.ts index b7e2173..4284a47 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -113,6 +113,10 @@ export function setCacheSizeOutput(cacheSize: number): void { core.setOutput("cache-size", cacheSize.toString()) } +export function setCacheMatchedKeyOutput(cacheMatchedKey: string): void { + core.setOutput("cache-matched-key", cacheMatchedKey) +} + type FindObjectResult = { item: minio.BucketItem; matchingKey: string; From 43300830525cdaaf578b6e434f554121afae9ad0 Mon Sep 17 00:00:00 2001 From: Jackie Li Date: Wed, 11 Mar 2026 16:52:44 +0000 Subject: [PATCH 2/3] test: verify cache-matched-key output in CI workflows --- .github/workflows/lookup-only.yaml | 7 +++++++ .github/workflows/test-env.yaml | 11 ++++++++++ .github/workflows/test.yml | 32 ++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/.github/workflows/lookup-only.yaml b/.github/workflows/lookup-only.yaml index 9990686..7faaad7 100644 --- a/.github/workflows/lookup-only.yaml +++ b/.github/workflows/lookup-only.yaml @@ -67,10 +67,17 @@ jobs: env: CACHE_HIT: ${{ steps.lookup.outputs.cache-hit }} CACHE_SIZE: ${{ steps.lookup.outputs.cache-size }} + CACHE_MATCHED_KEY: ${{ steps.lookup.outputs.cache-matched-key }} + EXPECTED_KEY: test-${{ runner.os }}-${{ github.run_id }} shell: bash run: | echo "CACHE_HIT $CACHE_HIT" echo "CACHE_SIZE $CACHE_SIZE" + echo "CACHE_MATCHED_KEY $CACHE_MATCHED_KEY" + if [ "$CACHE_MATCHED_KEY" != "$EXPECTED_KEY" ]; then + echo "cache-matched-key mismatch: got '$CACHE_MATCHED_KEY', expected '$EXPECTED_KEY'" + exit 1 + fi ls -R ~/test-cache || true ls -R test-cache || true src/create-cache-files.sh ${{ runner.os }} test-cache check_not_exist diff --git a/.github/workflows/test-env.yaml b/.github/workflows/test-env.yaml index 2b09bfc..0eb3c7b 100644 --- a/.github/workflows/test-env.yaml +++ b/.github/workflows/test-env.yaml @@ -49,6 +49,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: Restore cache + id: restore uses: ./ env: AWS_ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY }} @@ -62,6 +63,16 @@ jobs: path: | test-cache ~/test-cache + - name: Verify cache-matched-key output + shell: bash + env: + CACHE_MATCHED_KEY: ${{ steps.restore.outputs.cache-matched-key }} + EXPECTED_KEY: test-${{ runner.os }}-${{ github.run_id }} + run: | + if [ "$CACHE_MATCHED_KEY" != "$EXPECTED_KEY" ]; then + echo "cache-matched-key mismatch: got '$CACHE_MATCHED_KEY', expected '$EXPECTED_KEY'" + exit 1 + fi - name: Verify cache files in working directory shell: bash run: src/verify-cache-files.sh ${{ runner.os }} test-cache diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 21e6416..8c0dace 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,6 +46,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: Restore cache + id: restore uses: ./ with: endpoint: ${{ secrets.ENDPOINT }} @@ -57,6 +58,16 @@ jobs: path: | test-cache ~/test-cache + - name: Verify cache-matched-key output + shell: bash + env: + CACHE_MATCHED_KEY: ${{ steps.restore.outputs.cache-matched-key }} + EXPECTED_KEY: test-${{ runner.os }}-${{ github.run_id }} + run: | + if [ "$CACHE_MATCHED_KEY" != "$EXPECTED_KEY" ]; then + echo "cache-matched-key mismatch: got '$CACHE_MATCHED_KEY', expected '$EXPECTED_KEY'" + exit 1 + fi - name: Verify cache files in working directory shell: bash run: src/verify-cache-files.sh ${{ runner.os }} test-cache @@ -75,6 +86,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: Restore cache + id: restore-keys uses: ./ with: endpoint: ${{ secrets.ENDPOINT }} @@ -87,6 +99,15 @@ jobs: test-cache ~/test-cache restore-keys: test-${{ runner.os }}- + - name: Verify cache-matched-key output + shell: bash + env: + CACHE_MATCHED_KEY: ${{ steps.restore-keys.outputs.cache-matched-key }} + run: | + if [ -z "$CACHE_MATCHED_KEY" ]; then + echo "cache-matched-key should not be empty when restore-keys match" + exit 1 + fi - name: Verify cache files in working directory shell: bash run: src/verify-cache-files.sh ${{ runner.os }} test-cache @@ -194,6 +215,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: Restore cache + id: restore-only uses: ./restore/ with: endpoint: ${{ secrets.ENDPOINT }} @@ -205,6 +227,16 @@ jobs: path: | test-cache ~/test-cache + - name: Verify cache-matched-key output + shell: bash + env: + CACHE_MATCHED_KEY: ${{ steps.restore-only.outputs.cache-matched-key }} + EXPECTED_KEY: test-save-only-${{ runner.os }}-${{ github.run_id }} + run: | + if [ "$CACHE_MATCHED_KEY" != "$EXPECTED_KEY" ]; then + echo "cache-matched-key mismatch: got '$CACHE_MATCHED_KEY', expected '$EXPECTED_KEY'" + exit 1 + fi - name: Verify cache files in working directory shell: bash run: src/verify-cache-files.sh ${{ runner.os }} test-cache From cdd95a5eeff5155c2c84151d5090d7402b4977d6 Mon Sep 17 00:00:00 2001 From: Jackie Li Date: Wed, 11 Mar 2026 16:57:58 +0000 Subject: [PATCH 3/3] fix: guard cache-matched-key assertions on successful restore Only assert cache-matched-key value when cache-hit is true, since transient S3 failures result in empty output by design. --- .github/workflows/lookup-only.yaml | 2 +- .github/workflows/test-env.yaml | 1 + .github/workflows/test.yml | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/lookup-only.yaml b/.github/workflows/lookup-only.yaml index 7faaad7..9af678a 100644 --- a/.github/workflows/lookup-only.yaml +++ b/.github/workflows/lookup-only.yaml @@ -74,7 +74,7 @@ jobs: echo "CACHE_HIT $CACHE_HIT" echo "CACHE_SIZE $CACHE_SIZE" echo "CACHE_MATCHED_KEY $CACHE_MATCHED_KEY" - if [ "$CACHE_MATCHED_KEY" != "$EXPECTED_KEY" ]; then + if [ "$CACHE_HIT" = "true" ] && [ "$CACHE_MATCHED_KEY" != "$EXPECTED_KEY" ]; then echo "cache-matched-key mismatch: got '$CACHE_MATCHED_KEY', expected '$EXPECTED_KEY'" exit 1 fi diff --git a/.github/workflows/test-env.yaml b/.github/workflows/test-env.yaml index 0eb3c7b..22296f6 100644 --- a/.github/workflows/test-env.yaml +++ b/.github/workflows/test-env.yaml @@ -64,6 +64,7 @@ jobs: test-cache ~/test-cache - name: Verify cache-matched-key output + if: steps.restore.outputs.cache-hit == 'true' shell: bash env: CACHE_MATCHED_KEY: ${{ steps.restore.outputs.cache-matched-key }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8c0dace..fc29931 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,6 +59,7 @@ jobs: test-cache ~/test-cache - name: Verify cache-matched-key output + if: steps.restore.outputs.cache-hit == 'true' shell: bash env: CACHE_MATCHED_KEY: ${{ steps.restore.outputs.cache-matched-key }} @@ -100,14 +101,12 @@ jobs: ~/test-cache restore-keys: test-${{ runner.os }}- - name: Verify cache-matched-key output + if: steps.restore-keys.outputs.cache-matched-key != '' shell: bash env: CACHE_MATCHED_KEY: ${{ steps.restore-keys.outputs.cache-matched-key }} run: | - if [ -z "$CACHE_MATCHED_KEY" ]; then - echo "cache-matched-key should not be empty when restore-keys match" - exit 1 - fi + echo "cache-matched-key is set to: $CACHE_MATCHED_KEY" - name: Verify cache files in working directory shell: bash run: src/verify-cache-files.sh ${{ runner.os }} test-cache @@ -228,6 +227,7 @@ jobs: test-cache ~/test-cache - name: Verify cache-matched-key output + if: steps.restore-only.outputs.cache-hit == 'true' shell: bash env: CACHE_MATCHED_KEY: ${{ steps.restore-only.outputs.cache-matched-key }}