Caching
The loha/cache@v2 task saves and restores dependency directories between runs. A cache hit typically collapses a two-minute install into a five-second extract.
Caching is two steps, not one: a restore before your install command, and a save after it. There is no implicit "cache this step" wrapper.
Storage is provided
There is no bucket to create, no endpoint, and no credentials. On paid plans, caching is on for new repositories; for an existing one, a workspace admin enables it in the repository settings. Your build never holds a storage credential — it asks Loha for a URL naming one object and transfers it directly.
If caching is off for a run — switched off for the repository, or not in the workspace's plan — the cache steps log a warning and do nothing, and the build continues uncached. The same is true of a save that cannot reach cache storage: it warns and the build stays green. Set fail_on_miss: true (or FAIL_ON_MISS: true on the restore step) if a build must not run without its cache (restore only).
One workspace's caches are kept apart from every other workspace's automatically. Use CACHE_PREFIX to separate genuinely different caches within a repository (web vs api), not to separate branches — that already happens.
The short form
A cache: block on the job is the usual way to write this:
version: '1'
on:
push:
branches: [main]
jobs:
build:
cache:
paths: [node_modules, .npm]
key_files: [package-lock.json]
prefix: web
steps:
- name: Checkout
uses: loha/git-clone@v1
- name: Install
run: npm ci
That expands to exactly the restore/save pair below, and both steps appear on the
build page with their own logs. Prefer it: the two halves have to agree on
paths, key_files and prefix, and a hand-written pair that drifts apart is
not an error anywhere — it saves under one key and restores under another, which
presents as a cache that is simply always cold.
When the job's first step is loha/git-clone, the restore runs right after it (the lockfile named in key_files only exists once the repository is cloned); otherwise it runs first. The save always runs last. Because the restore runs after the checkout, a cached path that is also a committed file is overwritten by the cached copy, so don't cache paths that are tracked in your repository.
The long form
Writing the pair yourself is still supported, and is what the block above becomes:
version: '1'
on:
push:
branches: [main]
jobs:
build:
steps:
- name: Checkout
uses: loha/git-clone@v1
- name: Restore cache
uses: loha/cache@v2
with:
ACTION: restore
PATHS: node_modules,.npm
KEY_FILES: package-lock.json
CACHE_PREFIX: web
- name: Install
run: npm ci
- name: Save cache
uses: loha/cache@v2
with:
ACTION: save
PATHS: node_modules,.npm
KEY_FILES: package-lock.json
CACHE_PREFIX: web
How keying works
The cache key is derived, not written by you:
{CACHE_PREFIX}-{sha256(KEY_FILES)[:16]}
KEY_FILES is a comma-separated list of files whose contents define the cache, normally your lockfile. When any of them changes, the hash changes, restore misses, your install runs fresh, and save writes a new entry under the new key.
That means invalidation is automatic. You don't bump a version suffix; you change your dependencies and the key follows. To force a fresh cache anyway, change CACHE_PREFIX.
There is no KEY parameter and no RESTORE_KEYS fallback-prefix list. Loha's model is a single derived key per prefix, not GitHub Actions' primary-key-plus-fallbacks.
Parameters
The full table, including FAIL_ON_MISS and WORKING_DIRECTORY, is on the task's catalog page. There are only six, and the four you'll always set are ACTION, PATHS, KEY_FILES and CACHE_PREFIX.
PATHS must cover every directory your package manager populates, or the restore succeeds and the install re-downloads anyway. For npm that's both node_modules and .npm; for pip, .venv and .pip-cache.
Cache scope
Caches are scoped per repository and per branch, and the rules decide when you get a hit:
- A build reads its own branch's cache first, then falls back to your default branch's, so a new branch or pull request starts warm rather than cold.
- A build writes only its own branch's cache. A branch can never overwrite what your default branch later restores.
The second rule is a security boundary rather than a convenience. Without it, anyone able to push a branch could plant content that every later build on main would download and execute — a known attack against CI caches generally, not a Loha quirk.
Saves are idempotent per key, so concurrent runs can't corrupt each other.
Builds from forks
A pull-request build from a fork gets no cache token, so it can neither restore from nor write to your cache. The build shows a warning, cache steps do nothing, and the build continues uncached. A restore with FAIL_ON_MISS: true fails the build instead, because there is no cache to find.
What's not supported
- Multi-key restore with fallback prefixes: one derived key per prefix.
- Cross-workspace cache sharing: not planned; it would weaken isolation.
- Bring-your-own bucket: removed. Storage is managed, so there is no endpoint or credential to configure — and nothing in a build can name a bucket, which is what keeps one workspace out of another's cache.
- Cache analytics (hit rate, size trend): planned alongside a cache management UI.
The CACHE_HIT and CACHE_KEY results are exposed on the restore step if you want to branch on them in a later run: command.
See also
cachecatalog page: every parameter, plus npm/pip/cargo examples- Examples: caching alongside artifact upload