Skip to main content

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​