Releases: curlewlabs-com/local-cache
Release list
v3.2.0
What is new
Added — gc action for least-recently-used eviction (#38, closes #32)
A new curlewlabs-com/local-cache/gc action runs a scheduled sweep that evicts cold cache entries by last-use (--max-age-days, using the metadata mtime added in v3.1.0) and the per-runner restore-target copies they were rsynced to on each hit — the copies that otherwise accumulate unbounded alongside the store as version-keyed entries roll over.
It is race-free against concurrent saves and restores on the same machine: it takes the same per-key and per-target locks the save/restore paths hold (via curlewlabs-com/local-mutex), so evicting an entry can never corrupt a live rsync and reclaiming a target can never race a restore to it. As layered backstops, a target is spared if its owning entry is no longer cold or if anything under it was modified inside the window, and a live copy always keeps its entry. Default is a dry run (reports what would be reclaimed); pass apply: true to delete. See the README's gc section for the safety model and how to size max-age-days.
Changed
- Restore now takes its per-key lock on the matched entry's own key — for a
restore-keys(prefix) hit that is the resolved entry's stored key, not the requested key — so it is the exact lock the gc takes to evict that entry, closing a source-eviction race on prefix hits. Restore inputs and outputs are unchanged. - The restore / save / gc actions require
curlewlabs-com/local-mutexv2.1.0+ (pinned) for its CLI surface.
No breaking changes — action.yml and save/action.yml inputs and outputs are unchanged. Consumers tracking @v3 get the restore improvement automatically; the new gc action is opt-in.
v3.1.0
What is new
Added — last-use tracking for eviction (#33)
Every restore now stamps the entry metadata file (.local-cache-key) mtime, including the constant-time marker-skip path, so an external least-recently-used sweep can reclaim entries by genuine recency of use rather than creation — without evicting hot ones. The entry directory mtime (which drives prefix / restore-keys selection) is deliberately left untouched, so selection order is unchanged. The README has a new Eviction section with a safe whole-entry sweep recipe.
Changed
- Entry scripts now run
set -eu(nounset), with guarded positionals so a missing argument still produces the friendly "must not be empty" error instead of an unbound-variable abort. (#34) - CI: bump
actions/checkouttov6.0.3. (#30)
No breaking changes — action.yml and save/action.yml inputs and outputs are unchanged. Consumers tracking @v3 get this automatically.
v3.0.2
Summary
Docs-only release. No action behavior changes; @v3 consumers already received every code change through v3.0.1.
- AGENTS.md release-tagging bullet now documents the actual contract (immutable patch tag + floating major tag + GitHub release per release), matching local-mutex.
- README: fixed stale
curlewlabs-com/local-cache/save@v2reference in the Limitations section, cleaned up ambiguous pre-v3 wording in the "Upgrading from v1" section, and rewrote the Releasing section to match AGENTS.md.
Upgrade
Callers tracking @v3 stay on @v3 and get this change automatically. No code-level change vs v3.0.1.
v3.0.1
Summary
- Restore-target safety guard.
cache-restore.shnow refuses up-front (exit 2) if the caller-providedpathis whitespace-only, non-absolute, exactly///.//.., or equal to / an ancestor of\$HOME,\$RUNNER_WORKSPACE, or\$GITHUB_WORKSPACE. The check runs before any filesystem side effect (including--checkmode), so a misconfigured workflow can no longer wipe the runner through the unconditionalrm -rfon a stale-marker re-sync. - Dependency pin bump.
curlewlabs-com/local-mutexis now pinned to@v2.0.1. v2.0.1 hashes thenameinput with SHA-256 unconditionally, so long or special-character cache keys always map to distinct lockfiles (previous versions could collapse keys sharing a 200-char prefix into the same lock).
Behavior for safe callers
- No interface change. Inputs (
path,key,restore-keys,cache-dir) and outputs (cache-hit,cache-matched-key) are unchanged. - Existing workflows passing legitimate absolute paths (
/tmp/...,runner.tool_cache, etc.) see the same cache-hit/miss behavior as before.
Breaking-ish behavior for misconfigured callers
- Workflows that were relying on
path:expanding to the empty string, a relative path, or$HOMEwill now fail loudly at Phase 1 with exit 2 instead of silently wiping a directory. This is a bug in the calling workflow, not a regression in local-cache — but if you were depending on the old silent behavior, you'll see new CI failures after upgrading.
Upgrade
Callers tracking @v3 receive this automatically. Callers pinned to @v3.0.0 can move to @v3.0.1.
v3.0.0
First v3 release. Key changes from v2:
- Collision-safe key encoding: cache keys are SHA-256 hashed into fixed-length directory names. Distinct keys like
a/banda:bno longer collide. - Long key support: directory names are always 66 characters regardless of key length.
- Portability: CI runs on both Linux and macOS; GNU-only
findflags removed. - Shared helpers: encoding logic extracted to
lib/cache-common.sh.
Existing v2 and v1 cache entries are read transparently — no cache invalidation needed on upgrade. Just change @v2 to @v3 in your workflow files.
v2.1.0
What's new
Two-phase restore for steady-state optimization. The restore step now runs in two phases: Phase 1 checks the marker in parallel with no locking — if the target is already current, the restore finishes immediately. If not, Phase 2 acquires a per-key lock (shared with the save step via local-mutex) and performs the full rsync. Steady-state restores never contend on the lock.
LOCAL_CACHE_HIT and LOCAL_CACHE_MATCHED_KEY environment variables. The restore step now exports these to $GITHUB_ENV as a convenience for downstream steps that can't easily reference action outputs.
Output propagation fix. Phase 2 outputs (cache-hit, cache-matched-key) are now correctly propagated through the local-mutex composite action boundary via the new output-file passthrough in local-mutex v1.1.0.
Requires local-mutex@v1 ≥ v1.1.0 (automatic if tracking @v1).
v2.0.5
Critical comment-accuracy and test-strength fix. Previous comments at the post-acquire re-check claimed POSIX mv would refuse to rename onto an existing directory; this is wrong. POSIX mv src dest where dest is a directory NESTS src as dest/basename(src). The re-check is still load-bearing and correct, but for the right reason (preventing nested staging-dir corruption, not preventing a clean mv failure). The Sequential idempotent save test now also asserts no nested staging dir exists, verified locally to catch a removed-guard regression. Plus several smaller fixes: cache-matched-key assertion on sanitized exact hit, dot-key test motivation precision, README purge command now sweeps .tmp-* orphans, .gitignore cleanup.
v2.0.4
Round-3 audit follow-ups and local-mutex pin bump to @v1.0.4 (post-convergence; includes macOS UTF-8 locale fix). Comment accuracy: marker format header now correct for both exact and prefix hits; post-acquire re-check rationale now consistent across file. Test coverage: save-side dot-key rejection now exercised. Documentation precision: first-prefix-wins fallback semantics, accumulation behavior, SIGKILL orphan caveat, save action.yml cache-dir constraint.
v2.0.3
Audit follow-ups and local-mutex pin bump. No user-visible behavior changes on the happy path.
Changes
- Pin hygiene.
actions/checkout@v6pinned to@v6.0.2in all three CI jobs. Repo policy is hard-pinning on every ecosystem. - Save excludes restore marker.
lib/cache-save.shnow passes--exclude=.local-cache-restoreto rsync when copying the source into the new entry. On the canonical restore -> install -> save pattern, a prefix-hit restore previously left a stale marker file inside the new on-disk entry. Harmless at runtime but pollutes cache entries with stale metadata. - Prefix marker-skip is now covered by CI. Existing tests exercised exact-hit-skip and prefix-match-first-time but not prefix-match-second-time-skip (the
is_exact="false"branch inis_current). New CI steps cover both the marker-skip and the save-exclude behaviors. - Constant-time marker-skip restored.
lib/cache-restore.shpreviously ranfind ${entries_dir}/ | wc -lunconditionally to populate a::debug::log line, contradicting the README's constant-time claim on the happy path. The call is now gated behindRUNNER_DEBUG=1. MARKER_VERSIONconstant. The inlinev2:literal inlib/cache-restore.shis now a single constant referenced in bothis_currentand the marker write..github/workflows/ci.ymlkeeps its assertion literals with a comment pointing at the canonical source.- Marketplace name symmetry.
save/action.ymlrenamed toRunner Local Cache (Save)to matchaction.yml'sRunner Local Cache. - README polish.
rmcommand now usesentries/*in both examples (matching semantics). Marker-match wording clarified for the prefix-hit path. curlewlabs-com/local-mutex@v1.0.0->@v1.0.1. The sister action just shipped a patch release; local-cache consumes the latest.
Pinned users on @v2 pick this up automatically. Users pinned to @v2.0.2 can bump to @v2.0.3.
v2.0.2
Audit follow-ups: test clarity and documentation precision. No behavior changes. Renames a misleading CI test name (sequential save idempotency, not parallel mutex contention), adds inline comment to load-bearing post-acquire re-check in cache-save.sh, and tightens README wording about restore semantics.