Migration Guide
Migrating from official actions/cache or legacy tespkg/actions-cache to cloud-cache-action takes less than 2 minutes.
Automated Migration with AI Coding Agents
If you use an AI coding assistant or agent (such as Claude Code, Cursor, Copilot, Antigravity, Devin, Codex, or Gemini), copy and paste the prompt below into your agent.
The prompt instructs the agent to discover all cache steps in your repository, recommend or detect your cloud storage provider, perform 1:1 input preservation, and output a concise checklist of required GitHub Secrets.
Copy-Pastable Agent Prompt
Please migrate all GitHub Actions caching steps in this repository to `xSAVIKx/cloud-cache-action@v1`.
### Task Instructions:
1. **Scan & Discover Workflows**:
- Inspect all workflow files in `.github/workflows/` (and any custom composite actions).
- Find all steps using:
- `actions/cache@*`
- `actions/cache/restore@*`
- `actions/cache/save@*`
- `tespkg/actions-cache@*`
2. **Select Storage Provider & Secrets**:
- Check if this repository already uses cloud infrastructure secrets, or ask me which provider I prefer:
- **Cloudflare R2** (Recommended for zero egress fees):
- Inputs: `bucket`, `endpoint: https://${{ secrets.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com`, `access-key: ${{ secrets.R2_ACCESS_KEY_ID }}`, `secret-key: ${{ secrets.R2_SECRET_ACCESS_KEY }}`
- **AWS S3 with OIDC** (Recommended for AWS workloads, no static keys):
- Uses `aws-actions/configure-aws-credentials` with IAM role assumption, then `cloud-cache-action` with `bucket` and `key`.
- **AWS S3 (Static IAM)**:
- Inputs: `bucket`, `region`, `access-key: ${{ secrets.AWS_ACCESS_KEY_ID }}`, `secret-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}`
- **Google Cloud Storage (GCS)**:
- Inputs: `bucket`, `endpoint: https://storage.googleapis.com`, `access-key: ${{ secrets.GCS_HMAC_ACCESS_ID }}`, `secret-key: ${{ secrets.GCS_HMAC_SECRET }}`
- **Backblaze B2**:
- Inputs: `bucket`, `endpoint: https://s3.<region>.backblazeb2.com`, `access-key: ${{ secrets.B2_KEY_ID }}`, `secret-key: ${{ secrets.B2_APPLICATION_KEY }}`
- **Self-Hosted (MinIO / Garage / SeaweedFS / RustFS)**:
- Inputs: `bucket`, `endpoint`, `access-key`, `secret-key` (and `force-path-style: true` for MinIO).
- If I have not specified a provider yet, prompt me or default to **Cloudflare R2** or **AWS S3**.
3. **Perform 1:1 Step Replacement**:
- Replace `actions/cache@v...` with `xSAVIKx/cloud-cache-action@v1`.
- Replace `actions/cache/restore@v...` with `xSAVIKx/cloud-cache-action/restore@v1`.
- Replace `actions/cache/save@v...` with `xSAVIKx/cloud-cache-action/save@v1`.
- Preserve all existing cache keys and options: `path`, `key`, `restore-keys`, `lookup-only`, `fail-on-cache-miss`, `enableCrossOsArchive`, `read-only`.
- Add the required provider inputs (`bucket`, `endpoint`, `access-key`, `secret-key`) referencing GitHub Secrets (`${{ secrets.<SECRET_NAME> }}`).
4. **Verify & Summarize**:
- Check that all modified YAML files have valid syntax and proper indentation.
- List the modified workflow files with a summary of changes.
- Provide a clear checklist of the exact secret names I need to configure in GitHub Repository Settings (`Settings > Secrets and variables > Actions`).Required Secrets Cheat Sheet by Provider
Share this quick reference with your team or agent when setting up repository secrets:
| Provider | Required GitHub Secrets | Endpoint Format / Note |
|---|---|---|
| Cloudflare R2 | R2_ACCOUNT_IDR2_ACCESS_KEY_IDR2_SECRET_ACCESS_KEY | https://<account-id>.r2.cloudflarestorage.com(Zero egress fees) |
| AWS S3 (OIDC) | No static keys needed | Configured via aws-actions/configure-aws-credentials and IAM Role |
| AWS S3 (Static) | AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY | Specify region (e.g. us-east-1) |
| Google Cloud Storage | GCS_HMAC_ACCESS_IDGCS_HMAC_SECRET | https://storage.googleapis.com(Requires HMAC key for dedicated Service Account) |
| Backblaze B2 | B2_KEY_IDB2_APPLICATION_KEY | https://s3.<region>.backblazeb2.com |
| Fastly Storage | FASTLY_ACCESS_KEYFASTLY_SECRET_KEY | https://object.<region>.fastlystorage.com |
| Garage S3 | GARAGE_ACCESS_KEYGARAGE_SECRET_KEY | http://garage.internal:3900 |
| SeaweedFS S3 | SEAWEED_ACCESS_KEYSEAWEED_SECRET_KEY | http://seaweedfs.internal:8333 |
| MinIO S3 | MINIO_ACCESS_KEYMINIO_SECRET_KEY | http://minio.internal:9000(Requires force-path-style: true) |
| RustFS | RUSTFS_ACCESS_KEYRUSTFS_SECRET_KEY | http://rustfs.internal:9000(Set provider: rustfs) |
Manual Migration from actions/cache (v4, v5, v6)
Because cloud-cache-action maintains 1:1 input and output parity, you simply change the uses: line and add your bucket configuration:
Before (actions/cache):
- name: Cache dependencies
uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-After (cloud-cache-action):
- name: Cache dependencies
uses: xSAVIKx/cloud-cache-action@v1
with:
# 1. Add your S3 bucket & credentials
bucket: my-ci-cache-bucket
endpoint: https://${{ secrets.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com # Optional for AWS S3
access-key: ${{ secrets.S3_ACCESS_KEY }}
secret-key: ${{ secrets.S3_SECRET_KEY }}
# 2. Keep all your existing inputs exactly as they are
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-All existing features (lookup-only, fail-on-cache-miss, enableCrossOsArchive, read-only) continue to work identically. save-always is not supported: use cloud-cache-action/save with if: always() instead.
ACTIONS_CACHE_MODE and denied writes
GitHub sets ACTIONS_CACHE_MODE from the cache-mode workflow key, at the workflow level or at jobs.<job_id>.cache-mode. When the workflow does not set the key, the GitHub documentation gives a default of read for low-trust triggers, such as pull_request_target, issue_comment and workflow_run, that resolve to the default-branch scope. The pull_request event is not affected.
The action obeys ACTIONS_CACHE_MODE in the S3 tier, with the same rules as @actions/cache 6.2.0 (and the 5.2.0 backport):
noneskips the restore and the save.readskips the save.write-onlyskips the restore.write, an unset variable and an unknown value change nothing.
A skip logs one info line and does not fail the step, also in strict dual-cache mode. A skipped restore is a cache miss: cache-hit is false, and fail-on-cache-miss: true fails the step.
An S3-only job on one of those low-trust triggers can stop saving after the upgrade. To save from such a job again, set cache-mode: write on the job. Only do this when the job cannot run untrusted code.
When the storage refuses an S3 save with AccessDenied (or a 403 that is not a credential error), the step warns that the credentials may not be allowed to write to the bucket and that read-only: true skips the save. The job does not fail, except in strict dual-cache mode.
Manual Migration from tespkg/actions-cache
If you are using legacy tespkg/actions-cache, migrating to cloud-cache-action solves multiple architectural issues:
- Active Runner Support: Runs natively on Node 24 (
node24) with zero runner deprecation warnings. - Official AWS SDK v3: Avoids
minio-jsURL signing and header quirks with Cloudflare R2 and Fastly. - Cross-Platform Path Safety: Normalizes Windows cache keys to POSIX forward slashes (
/), avoiding broken backslash keys in object storage. - Modern Parity: Includes
actions/cache@v6features such aslookup-only,fail-on-cache-miss, andread-only.
Input Differences:
- All legacy inputs (
bucket,endpoint,region,insecure,accessKey,secretKey,sessionToken) remain supported for backward compatibility. - Kebab-case aliases are also supported (
access-key,secret-key,session-token,force-path-style). - Fallback Behavior:
use-fallbackisfalseby default incloud-cache-actionrather thantrue. If you want automatic GitHub Actions Cache fallback on S3 errors or cache miss, setuse-fallback: true.
Branch Isolation and Trust Model
Like actions/cache, restores only use caches from the current ref, the pull request's base branch and the default branch. A feature branch can reuse main's cache, but main never restores a cache that a feature branch saved. Set scoped-to-ref: false to share caches across every ref.
Unlike GitHub's cache service, the bucket itself does not enforce this: anyone who holds the bucket's write credentials can write any cache object, including ones main will restore. Archives are extracted with absolute paths allowed, as actions/cache does. So:
- Do not expose cache credentials to workflows that run untrusted code, such as
pull_request_targetjobs that check out fork code. - Prefer short-lived credentials (OIDC) scoped to the cache bucket or prefix.
- Use a separate bucket or
prefixfor caches that must not be shared between repositories or trust levels.
Tag-triggered runs and refs
The Git ref segment in the object key comes from whatever triggered the run. A run started by pushing a tag saves its cache under refs/tags/<tag>, which is a ref no pull request or branch build will ever restore from, since restores only search the current ref, the pull request base branch and the default branch — never arbitrary tags. If a release workflow triggered by a tag push is your only place that populates a cache other jobs expect to reuse, that cache will never be found.
Prefer saving on the default branch instead — a push to main or a manual workflow_dispatch run there — so the cache is saved under a ref other jobs actually search. If you do need caches shared across tags, branches and pull requests, set scoped-to-ref: false together with the trust model warning above: every ref then reads and writes the same object, so anyone who can push a branch or tag can also overwrite what main restores.