Releases: xSAVIKx/cloud-cache-action
Release list
v1.5.0 – RustFS Support
No breaking changes, and nothing changes for an existing workflow: this release only adds a
provider.
Added
- RustFS provider preset.
provider: rustfsselects path-style addressing and theus-east-1
default region for RustFS, the Apache-2.0 Rust object store that reached
1.0 in September 2026. The full integration suite runs against RustFS 1.0.0 in CI, next to
Garage, SeaweedFS and MinIO. Ranged downloads, multipart uploads, object tagging, user metadata
and conditional writes all work, so no feature is disabled for it.- RustFS serves S3 on port 9000, the port MinIO uses, so endpoint sniffing cannot tell the two
apart and reportsminio. Both presets resolve to the same settings; setprovider: rustfs
to name it in the log.
- RustFS serves S3 on port 9000, the port MinIO uses, so endpoint sniffing cannot tell the two
v1.4.0 – Parallel Downloads, Upload Concurrency & Transfer Benchmarks
No breaking changes. Caches saved by v1.1, v1.2 and v1.3 stay valid.
Added
- Parallel downloads. A restore now fetches any archive larger than
download-chunk-size
(default8388608, 8 MiB) as concurrentRangerequests,download-concurrency(default8,
1–32) at a time, in both file and streaming mode, the same fan-outactions/cacheuses with a
larger block (it uses 4 MiB; 8 MiB measured faster on every provider). Each part is retried on
its own. Archives no larger than one chunk, and every restore withdownload-concurrency: 1,
use a single request as before. A provider that answers a ranged
request with the whole object logs
s3://<bucket>/<key> does not support ranged GET requests; downloading it in one request.and
gets the single request. The restore's metrics line reports the part count asdownloadParts. upload-concurrencyinput (default8, 1–32) on the main andsaveactions: how many
multipart parts a save sends at once, in both file and streaming mode. It was fixed at 4.- Transfer benchmark workflow (
benchmark.yml, manual) that measures save and restore speed
for several transfer settings against the live providers, and a "Transfer Performance" guide
with the measured tables and the settings that measured best.
Changed
- Upload defaults follow
actions/cache. The defaultupload-chunk-sizeis 64 MiB instead of
10 MiB and a save sends 8 parts at once instead of 4, so a save may hold up to 512 MiB of parts
in memory. Setupload-concurrency: 4andupload-chunk-size: 10485760to keep the v1.3
footprint. Anupload-chunk-sizebelow 5 MiB or above 128 MiB now warns and uses the default;
before, a small value was silently replaced by 10 MiB.
v1.3.0 – Object Metadata & Tags, Explain & Inspect, Metrics
No breaking changes. Caches saved by v1.1 and v1.2 stay valid: the key layout, ${version}
hashing and archive format are unchanged, and a save that sets none of the new inputs writes the
same object as v1.2 did.
Added
- Object metadata and tags. The new
metadatainput stores user metadata (x-amz-meta-*) on
every saved cache object and the newtagsinput sets object tags, onekey=valueper line.- Keys starting with
cloud-cache-are reserved, metadata is capped at 2048 bytes in total
(including thecloud-cache-sha256entry the save adds) and tags at 10, with S3's tag
character set enforced. An invalid value fails the step before any S3 call. - Providers without object tagging log one warning
(s3://<bucket> could not store object tags (<reason>); saved without them.) and save without
tags.
Garage accepts theTaggingheader but implements no tagging API, so its tags cannot be read
back and should be assumed dropped. - With
streaming: truethe metadata is attached by a copy of the object onto itself after the
upload; a provider that cannot do that copy costs the metadata, not the cache.
- Keys starting with
cache-metadataoutput. The restored object's user metadata as a JSON object, excluding
cloud-cache-*keys;{}when there is none, on a GitHub-tier hit, or with
lookup-only: true, which never downloads the object.- A sha256 on streaming saves. A
streaming: truesave now attachescloud-cache-sha256
once the stream has finished, so streamed archives get the same integrity check on restore as
file-mode ones. The attach is best-effort: if it fails, the save still succeeds and the object
simply carries no checksum. explaininput (defaultfalse) on the main andrestoreactions. It logs the whole cache
lookup into aCache lookup explainedgroup, and into a job summary section of the same name,
before the restore runs: the raw and resolveds3-key-pattern, the${version}hash and the
paths, compression method and cross-OS flag it is computed from, the refs and tier order, every
listing performed, every candidate object with the version it carries, and a plain-language
reason for the outcome. Building the report never fails the step.cloud-cache-action/inspectsub-action that reports which object a restore would use, and
why, without restoring. It only lists objects — it downloads nothing and writes nothing.- Outputs:
would-hit,would-match-key,would-match-object,candidate-count,report
(the full report as JSON, replaced by a{"truncated":true,…}summary beyond 64 KB) and
cache-storage-provider. max-candidates(default20) caps how many objects the report shows per search; every
object under the prefix is still listed, as a restore does, and the rest are counted as
"… and N more not shown".fail-on-cache-miss: truefails the step when nothing would be
restored.- The report lists objects and never
HEADs the exact key, so on a provider with eventually
consistent listings a report taken right after a save can say "would miss" where a restore
would hit.
- Outputs:
- Metrics outputs.
cache-restore-duration-ms,cache-save-duration-ms,
cache-transfer-duration-msandcache-bytesare set on every path, so they are always
defined (0when the step did not complete or transferred nothing). metrics-fileinput on the main,restore,save,pruneandinspectactions. Every
step writes onecloud-cache-metrics <json>debug line, and appends the same JSON as one line to
this file when it is set, resolved relative toGITHUB_WORKSPACE.transferDurationMsmeasures the S3 transfer alone in file mode; withstreaming: trueit
covers download-plus-extract, and the line'sstreamingfield tells the two apart.pruneandinspectwrite their line once the step has finished its work, so a step that
fails earlier writes none.- Writing the file is best-effort: a failure only warns and never fails the step.
- Documentation: a new "Inspecting Lookups" guide on the documentation site, plus metrics
sections in the README and the Getting Started and Pruning guides.
Changed
cache-etagon streaming saves. Withstreaming: truethe output now reports the ETag of
the object after its metadata copy, rather than the multipart upload's ETag.- Live cloud CI gating. The Amazon S3, Cloudflare R2 and Google Cloud Storage suites now run on
main, nightly and on manual dispatch, and on a pull request only when it carries thefull-ci
label. Every self-hosted-S3 job (Garage, SeaweedFS, MinIO) still runs on every pull request.
v1.2.0 – Pruning, Integrity Checks, Safe Concurrent Saves & Opt-in Streaming
No breaking changes. Caches saved by v1.1 stay valid, and the default save and restore paths still
work the same way, apart from the additions below.
Added
cloud-cache-action/prunesub-action that deletes cache archives older than
older-than-days. It supportsref,scoped-to-ref,scoped-to-repository,prefixand
dry-run.- It only deletes objects whose whole key matches the resolved
s3-key-pattern, so other
repositories, other refs and non-archive objects under the same listing prefix are never
touched. - It refuses to run when a pattern can't be scoped safely.
- An invalid
dry-runvalue fails the step before any S3 call.
- It only deletes objects whose whole key matches the resolved
- Archive integrity. File-mode saves store a sha256 of the archive in the
cloud-cache-sha256object metadata, and restores verify it.- A mismatch counts as a cache miss with a warning.
- It only fails the step when both
dual-cacheanddual-cache-strictaretrue. - Objects without the metadata (any v1.1 cache, or a streamed save) skip the check.
- Safe concurrent saves. Saves send
If-None-Match: *, so when two jobs save the same key,
the first one wins and the second keeps the existing cache.- If a provider rejects the condition, the save retries once without it and doesn't send it
again for the rest of the run. - A
409 ConditionalRequestConflictis retried once. - Providers that ignore the condition, such as Garage, keep last-writer-wins.
- If a provider rejects the condition, the save retries once without it and doesn't send it
- Job summary. Restore and save each write a step summary table with the key, hit and
source, size and duration. The newjob-summaryinput defaults totrue. - Opt-in streaming (
streaming: true, defaultfalse): pipes tar straight to a multipart
upload and downloads straight into tar, with no temporary archive file.- The file-based path stays the default and is unchanged.
- A streamed upload is only completed after tar exits successfully, so a failed tar never
leaves a truncated cache. - It falls back to file mode for BSD tar with zstd on Windows and for rejected conditions.
- Maintenance. Dependabot for npm and GitHub Actions, with minor and patch updates grouped
and major updates proposed separately. - Release workflow (
.github/workflows/release.yml), which runs when a GitHub release is
published.- It verifies the tag against
package.jsonand checks thatdistis up to date. - It then moves the major tag, but only for the highest stable release of that major.
- It verifies the tag against
Changed
- The main action,
restoreandsavegain thejob-summaryandstreaminginputs. - A bare
$ref,$key,$prefix,$version,$archive_filenameor$GITHUB_REPOSITORYin
s3-key-patternis no longer expanded from the environment. It stays literal and logs one
warning per name.
Fixed
- A strict dual-cache save where
pathmatches nothing no longer fails the step. The GitHub
tier's "Path Validation Error" now counts as a skipped save, as it already did for the S3 tier. - With
scoped-to-repository: falseorscoped-to-ref: false, removing the placeholder no longer
leaves a leading, doubled or trailing/. A/is only removed when the placeholder fills a
whole path segment, so custom patterns keep their v1.1 keys. - When
CompleteMultipartUploadfails (for example, a lost save race), the multipart upload is
aborted instead of leaving billed parts behind. - Errors rewrapped by the restore and save steps keep the original error as
cause.
Full changelog: v1.1.0...v1.2.0
v1.1.0 – actions/cache-Compatible Paths, Versioned Ref-Scoped Keys & Cross-OS CI
🚀 Cloud Cache Action v1.1.0
v1.1 is a correctness release. path now behaves like actions/cache, S3 keys carry the Git ref and a cache version, dual-cache strict mode actually fails the step, and CI covers Linux, macOS, Windows, cross-OS restores and three self-hosted S3 servers.
Important
Caches saved by v1.0 are not reused. The S3 object layout changed, so every cache misses once on the first v1.1 run and is rebuilt. The action never reads v1.0 objects again; let a bucket lifecycle rule expire them.
⚠️ Breaking changes
- New key layout:
${GITHUB_REPOSITORY}/${prefix}${ref}/${key}/${version}/${archive_filename}.${version}hashes thepathpatterns, the compression method and, on Windows,enableCrossOsArchive, so a cache is never restored into a job that caches different paths. - Branch isolation like actions/cache: restores search the current ref, then the pull request base branch, then the default branch.
mainnever restores a feature branch's cache. Set the newscoped-to-ref: falseinput to share caches across all refs. save-alwaysremoved: it never worked, becausepost-ifcan't read inputs. To save after failed steps, usecloud-cache-action/restoreandcloud-cache-action/savewithif: always().dual-cache-strategy: independentremoved: it now logs a warning and behaves asbackfill.dual-cache-strict: truefails the step: it now fails on tier errors during restore and save. Previously save errors only produced warnings.
🐛 Fixes
pathpatterns go through@actions/glob, so~/.npm,**/node_modulesand!exclusions work. Previously they reached tar unexpanded and failed withCannot stat.- Archives store paths relative to
GITHUB_WORKSPACE.
- Archives store paths relative to
- tar selection matches actions/cache:
- GNU tar on Linux;
gtaror BSD tar on macOS; Git's GNU tar or System32 tar on Windows. - Two-step zstd with BSD tar on Windows.
MSYS=winsymlinks:nativestrictfor symlinks.- File names starting with
-can no longer inject tar options.
- GNU tar on Linux;
- Restore-key matching:
- Listing now reads every page. It used to stop after 100 keys, which made the "newest" match arbitrary.
- The primary key is tried as a prefix before
restore-keys, as in actions/cache. - Keys containing
/or$survive the round trip.
- Failed downloads or extractions count as a cache miss with a warning instead of failing the job.
backfillchecks the other tier first: it only archives and uploads to a tier that doesn't already have the key.- Retries:
retry-countnow sets the SDK's standard retries.- Stream retries only cover network failures the SDK doesn't retry itself.
- 403s and other permanent errors are no longer retried.
retry-count: 0is honoured.
- Checksums: S3-compatible providers (R2, GCS, B2, Garage, MinIO, …) only receive request checksums when S3 requires them.
- Warnings: unknown providers, invalid booleans and enum values, and a lone access or secret key now log a warning instead of being ignored.
- Consistency: the post step reuses the restore step's settings and compression method, so both steps compute the same object key.
- Branding: the
restoreandsavesub-actions now use valid icons.
✅ Testing
- Test suites: unit tests on real temporary directories, real tar round trips (symlinks, file modes, unicode, option-like file names), and a contract test that keeps all three
action.ymlmanifests in sync with the code. - Integration tests run the real restore and save code against SeaweedFS, MinIO and Garage, and fail instead of skipping when no server is available.
- CI on every pull request:
- Jest on Linux, macOS and Windows
- per-OS round trips through
./saveand./restore - post-step saves by the main action
- Linux → macOS/Windows cross-OS restores
- dual-cache against the live GitHub Actions Cache
- a strict-mode failure check
- actionlint
- Nightly: live cross-OS restores on Cloudflare R2.
📦 Upgrading
- uses: xSAVIKx/cloud-cache-action@v1 # now v1.1.0
with:
bucket: my-ci-cache-bucket
access-key: ${{ secrets.S3_ACCESS_KEY }}
secret-key: ${{ secrets.S3_SECRET_KEY }}
path: |
~/.npm
packages/*/node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-save-always: if you set it, remove it and use the restore/save split shown in the README.- Custom
s3-key-pattern: add${ref}and${version}to it. The action warns when they're missing. - Storage: ref scoping stores a separate cache per branch and per pull request, so configure a bucket lifecycle rule (for example, expire after 30–60 days).
- Security: read Branch isolation and trust model. Anyone holding the bucket's write credentials can write cache objects.
Full changelog: v1.0.0...v1.1.0
v1.0.0 – Production Release: Universal S3 Cache with 1:1 actions/cache Parity
🚀 Cloud Cache Action v1.0.0 (Production Release)
Cloud Cache Action is a production-ready, high-performance GitHub Action for saving and restoring cache artifacts to any S3-compatible cloud or self-hosted object storage with full 1:1 actions/cache parity (v4–v6) and native Node 24 execution.
✨ Highlights & Features
- 1:1
actions/cacheParity: Complete drop-in replacement for all official inputs (path,key,restore-keys,fail-on-cache-miss,lookup-only,enableCrossOsArchive,read-only,save-always) and outputs (cache-hit,cache-primary-key,cache-matched-key). - Modern Node 24 Runtime: Native
runs: using: 'node24'runtime with zero deprecation warnings on modern GitHub runners. - Universal Cloud Provider Compatibility: Live verified against:
- AWS S3 (both static IAM credentials and GitHub Actions OIDC role assumption)
- Cloudflare R2 (zero egress cost caching)
- Google Cloud Storage (GCS) (via XML API and HMAC service account keys)
- Backblaze B2
- Fastly Object Storage
- Garage S3 & SeaweedFS (self-hosted distributed object storage)
- MinIO / LocalStack
- ⚡ Dual-Caching Synchronization: Cache simultaneously across both your S3 bucket and GitHub Actions Cache tier with configurable priority (
s3-firstorgithub-first) and automatic backfill synchronization. - Custom S3 Key Templating: Flexible patterns such as
${GITHUB_REPOSITORY}/${prefix}${key}/${archive_filename}with support for custom environment variables (${RUNNER_OS},${GITHUB_JOB}). - Safe Cross-Platform Keys: Guarantees standard POSIX forward slashes (
/) in object storage across Linux, macOS, and Windows runners. - Resilient Archiving & Transfer: Multi-threaded compression (
zstdwith automaticgzipfallback), streaming multipart uploads, and exponential backoff retries on transient network errors. - Modular Sub-Actions: Dedicated restore-only (
restore/action.yml) and save-only (save/action.yml) workflows for custom build pipelines.
📦 Quick Start
- name: Cache dependencies to S3
uses: xSAVIKx/cloud-cache-action@v1
with:
bucket: my-ci-cache-bucket
endpoint: https://<account_id>.r2.cloudflarestorage.com # Or AWS, GCS, B2, MinIO
access-key: ${{ secrets.S3_ACCESS_KEY }}
secret-key: ${{ secrets.S3_SECRET_KEY }}
path: |
~/.npm
node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-Dual-Caching (S3 + GitHub Actions Cache)
- name: Dual Cache (S3 + GitHub Cache Fallback)
uses: xSAVIKx/cloud-cache-action@v1
with:
bucket: my-ci-cache-bucket
dual-cache: true
restore-priority: github-first
dual-cache-strategy: backfill
access-key: ${{ secrets.S3_ACCESS_KEY }}
secret-key: ${{ secrets.S3_SECRET_KEY }}
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}🧪 Verification & Reliability
- 138 Automated Tests: Unit tests, contract parity validation, and live container integration tests.
- Dedicated Provider Verification: Automated CI test suites running live against Amazon S3, Cloudflare R2, Google Cloud Storage, and MinIO.
- Dogfooded: Powers caching for repository CI and documentation deployment.
Documentation: https://xsavikx.github.io/cloud-cache-action/
Created by: Yurii Serhiichuk
v0.1.0 – Universal S3 Cache with 1:1 actions/cache Parity & Dual-Caching
🚀 Introducing Cloud Cache Action (v0.1.0)
Cloud Cache Action is a drop-in, high-performance GitHub Action for saving and restoring cache artifacts to any S3-compatible cloud or self-hosted object storage with full 1:1 actions/cache parity and native Node 24 execution.
✨ Key Features
- 1:1
actions/cacheParity: Complete drop-in replacement for official inputs (path,key,restore-keys,fail-on-cache-miss,lookup-only,enableCrossOsArchive,read-only,save-always) and outputs (cache-hit,cache-primary-key,cache-matched-key). - Modern Node 24 Runtime: Native
runs: using: 'node24'runtime with zero deprecation warnings. - Universal S3 Compatibility: Tested and verified across AWS S3, Cloudflare R2, Google Cloud Storage, Backblaze B2, Fastly Object Storage, Garage, SeaweedFS, MinIO, and custom endpoints.
- ⚡ Dual-Caching Synchronization: Cache simultaneously to both your S3 bucket and GitHub Actions Cache tier for maximum resilience, warm local runners, and rapid remote builds.
- Resilient Archiving & Transfer: Multi-threaded compression (
zstdwith automaticgzipfallback), chunked streaming uploads, and exponential backoff retries on transient network errors. - Modular Sub-actions: Dedicated restore-only (
restore/action.yml) and save-only (save/action.yml) workflows for custom build step placement.
📦 Quick Start
- name: Cache Dependencies (S3)
uses: xSAVIKx/cloud-cache-action@v0
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
bucket: my-ci-cache-bucket
aws-region: us-east-1
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}Dual-Caching Example (S3 + GitHub Actions Cache)
- name: Dual Cache (S3 + GitHub Cache Fallback)
uses: xSAVIKx/cloud-cache-action@v0
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
bucket: my-ci-cache-bucket
dual-cache: true
cache-source: dual
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}Documentation: https://xsavikx.github.io/cloud-cache-action/
Website: https://serhiichuk.dev