Three gaps let a cache key live only in the GitHub cache with GCS
configured, and one of them stopped the pnpm store (1.6 GB) from ever
reaching GCS in cula-platform:
- A hit served by the GitHub fallback counted as an exact match, so the
post step never saved it. Once GCS missed a key a single time, the
GitHub copy satisfied every later job and GCS never got the key.
Restore now records which backend answered (State.CacheSource); on a
GitHub hit the save writes the entry to GCS, without a second GitHub
save that would only fail on the existing entry.
- The post step of an action nested inside composite actions cannot see
sibling step outputs, so `path: ${{ steps.x.outputs.y }}` arrived empty
there ("Input required and not supplied: path") and the save was
skipped. Restore now records the resolved paths (State.CachePaths) and
the save falls back to them when the input is empty.
- saveCache logged "falling back to GitHub cache" on an empty GCS result
and then returned without doing so. The fallback now runs in both the
empty-result and the thrown-error case, unless the caller opted out.
The restore/save tests mocked core.getState with positional
mockImplementationOnce chains that depended on the number and order of
reads (and leaked across tests); they are keyed by state name now.
dist/ rebuilt with `npm run build`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y5UfALF3nKcwCLWPikbnJn
@ -103,6 +103,17 @@ If you are using a `self-hosted` Windows runner, `GNU tar` and `zstd` are requir
See [Skipping steps based on cache-hit](#skipping-steps-based-on-cache-hit) for info on using this output
### Google Cloud Storage backend
When `gcs-bucket` (or the `CULA_CACHE_GCS_BUCKET` / `CONFIGURED_GCS_BUCKET` environment variable) is set, GCS is the primary backend and the GitHub cache is the fallback:
* **Restore** looks in GCS first (primary key, then `restore-keys`) and only then in the GitHub cache. The matched key is reported the same way for both, so `cache-hit` keeps its meaning.
* **Save** always goes to GCS. If the GCS upload fails, the entry is saved to the GitHub cache instead.
* **A hit served by the GitHub cache is written to GCS anyway.** Without this, a GitHub-only entry (from an earlier GCS failure) would satisfy every later job and GCS would never receive the key.
* **The `path` input is remembered from the restore step.** In a nested composite action the post step cannot see sibling step outputs, so a `path: ${{ steps.x.outputs.y }}` arrives empty there; the save then uses the paths restore resolved.
Objects are stored as `<gcs-path-prefix>/<key>.cache.tzst`; authentication uses Application Default Credentials, so the credentials must still exist when the post step runs (put `google-github-actions/auth`**before** this action in the job, its cleanup runs in reverse order).
### Cache scopes
The cache is scoped to the key, [version](#cache-version), and branch. The default branch cache is available to other branches.