Cache
Cache configuration describes what directories and files to preserve between workflow runs, and how cache keys are computed. Caching expensive-to-recompute directories (package manager stores, build caches) can dramatically reduce subsequent run times.
For runtime cache behavior (where hit/miss shows up, host vs Docker constraints), also see:
Quick example
check:
stage: ci
target: linux
cache:
key:
prefix: loom-cache
files:
- pnpm-lock.yaml
paths:
- .pnpm-store
- .nx/cache
policy: pull-push
when: always
script:
- pnpm install --frozen-lockfile
- pnpm nx run-many -t check
Schema rules
The schema validator enforces these constraints (verified via loom check):
Cache forms
| Form | Description |
|---|---|
| Mapping (single cache) | A single cache configuration applied to the job. |
| Sequence (multiple caches) | A list of named cache entries, each operating independently. |
null or [] | Explicitly disables caching for the job, overriding any default.cache. |
Cache subkeys
| Subkey | Required | Description |
|---|---|---|
paths | Yes (unless disabled: true) | Non-empty list of directories/files to cache. |
key | No | Cache key as a string or structured mapping. |
fallback_keys | No | Fallback keys tried in order if the primary key misses. |
policy | No | When to restore/save. Default: pull-push. |
when | No | Save based on job status. Default: on_success. |
name | Only in sequence form | Unique name identifying the cache entry. |
disabled | No (sequence only) | Disable a named cache entry without removing it. |
Unknown keys produce: remove unknown cache key; allowed keys are name, disabled, paths, key, fallback_keys, policy, when.
paths
Non-empty list of non-empty strings. Each string is a path relative to the project workspace root.
cache:
paths:
- .pnpm-store
- .nx/cache
- node_modules
For Docker jobs (image: set), paths refer to directories inside the container's workspace mount. Mismatches between host and container paths are a common source of "cache did nothing" issues — see Operational constraints.
key
Cache key determines which cached archive to restore and where to save. All runs that produce the same key share the same cached data.
String form
A non-empty string that supports template variable placeholders.
cache:
key: "pnpm-${job_name}-${head_sha}"
paths:
- .pnpm-store
Mapping form
A mapping with an optional prefix and a required files list.
cache:
key:
prefix: loom-cache
files:
- pnpm-lock.yaml
paths:
- .pnpm-store
| Subkey | Required | Description |
|---|---|---|
prefix | No | Non-empty string prepended to the computed key. Supports template placeholders. |
files | Yes | Non-empty list of file paths or glob patterns. When file contents change, a new cache key is generated. |
Glob support in files: entries support *, ?, and ** (doublestar recursive semantics). For example, "**/go.sum" matches go.sum files in any subdirectory. This is a Loom extension — many other CI systems do not support glob patterns in cache key files.
cache:
key:
prefix: loom-cache-go
files:
- go.work
- go.work.sum
- "**/go.sum"
paths:
- .go
If key is omitted, the runtime uses a default key computation.
fallback_keys
Non-empty list of non-empty strings tried in order when the primary key misses. Supports template variable placeholders.
cache:
key: "pnpm-${head_sha}"
fallback_keys:
- "pnpm-main"
- "pnpm-default"
paths:
- .pnpm-store
policy
Controls when the cache is restored and saved.
| Value | Restore at start | Save after finish | Use case |
|---|---|---|---|
pull-push (default) | Yes | Yes | Standard: restore and update the cache each run. |
pull | Yes | No | Read-only: parallel jobs that consume but don't update the cache. |
push | No | Yes | Write-only: jobs that build/populate the cache for others. |
install:
stage: deps
target: linux
cache:
paths: [.pnpm-store]
policy: pull-push
script:
- pnpm install
lint:
stage: ci
target: linux
cache:
paths: [.pnpm-store]
policy: pull
script:
- pnpm run lint
In this example, install both restores and saves the cache. lint only restores it, avoiding a redundant save since the cache contents didn't change.
when
Controls when the cache is saved based on the job's exit status.
| Value | Behavior |
|---|---|
on_success (default) | Save only when the job succeeds. |
on_failure | Save only when the job fails. |
always | Save regardless of exit status. |
check:
stage: ci
target: linux
cache:
paths: [.pnpm-store]
when: always
script:
- pnpm install
- pnpm test
Use when: always when the cached artifacts (like installed dependencies) are valid regardless of whether the job's tests pass or fail.
name
Required when cache is a sequence. Each entry must have a unique name.
Duplicate names produce: use unique cache names; "xyz" is duplicated.
default:
cache:
- name: pnpm
key:
prefix: loom-cache
files: [pnpm-lock.yaml]
paths: [.pnpm-store]
policy: pull-push
when: always
- name: go
key:
prefix: loom-cache
files: [go.work, "**/go.sum"]
paths: [.go]
policy: pull-push
when: always
disabled
Set disabled: true in a sequence entry to disable that named cache without removing it. When disabled, paths is not required.
check:
stage: ci
target: linux
cache:
- name: pnpm
disabled: true
- name: go
paths: [.go]
script:
- go test ./...
Disabling cache entirely
To override a default.cache for a specific job, set cache to null or []:
default:
cache:
paths: [.pnpm-store]
policy: pull-push
when: always
build-image:
stage: deps
target: linux
cache: []
script:
- docker build -t myapp .
Cache key template variables
When cache.key is a string, or in cache.key.prefix and cache.fallback_keys entries, these placeholders are expanded at runtime:
| Placeholder | Description |
|---|---|
${job_name} | Job name (graph node id). |
${job_id} | Job id (same as job name today). |
${run_id} | Executor run id. |
${pipeline_id} | Executor pipeline id. |
${head_sha} | Snapshot HEAD commit SHA. |
cache:
key: "pnpm-${job_name}-${head_sha}"
paths:
- .pnpm-store
Runtime behavior
During local execution, cache operations run as system sections around the selected job runtime provider (Host or Docker). Cache activity is recorded in structured runtime logs, not in your script: output.
Where cache events appear
Cache operations produce two system sections per job:
| Section | Timing | Purpose |
|---|---|---|
cache_restore | Before script runs | Restores cached archives matching the key (or fallback keys). |
cache_save | After script finishes | Saves the cached paths if policy and when conditions are met. |
These system sections appear in the runtime logs at:
.loom/.runtime/logs/<run_id>/jobs/<job_id>/system/cache_restore/events.jsonl
.loom/.runtime/logs/<run_id>/jobs/<job_id>/system/cache_save/events.jsonl
Follow the Diagnostics ladder to navigate from the pipeline summary to these events.
Cache store
Cached archives are stored locally under .loom/.runtime/cache/. Each archive is identified by a scope hash (derived from the repository remote URL or workspace path) and a key hash. The cache store tracks manifests that record the key, paths, and creation time for each archive.
Key design checklist
Use this checklist to avoid the most common "cache is useless" failures:
- Pick stable
paths: cache directories that are expensive to recompute and safe to reuse (package manager stores, build caches). Keep them workspace-relative (e.g.,.pnpm-store,.nx/cache). - Choose
key.filesthat fully describe the cache contents: include lockfiles and any configuration that changes the generated output (e.g.,pnpm-lock.yaml,package.json, tool config). - Avoid key collisions: scope your key with
${job_name}or a distinctprefixso unrelated jobs don't overwrite each other. Use multi-cache (name) for different concerns. - Decide when to include
${head_sha}:- Include it when correctness matters more than reuse (per-commit caches, fewer stale mismatches).
- Omit it when reuse matters more than strictness (branch/shared caches), but be prepared to invalidate keys when behavior changes.
- Multi-cache: use one cache entry per concern (e.g.,
pnpmvsgo) so you can invalidate and iterate independently.
Operational constraints
These are the constraints that show up as "cache didn't restore" or "cache saved but didn't help":
| Constraint | Impact |
|---|---|
| Permissions | The runtime must be able to read/write each cache.paths directory. For Docker jobs, the container user must have write permission inside the mounted workspace. |
| Disk usage | Large caches grow quickly and affect performance and local disk pressure. Cache fewer, high-value directories rather than the whole workspace. |
| Key staleness | If cache keys don't change when inputs change, stale state is restored. Use key.files and ${head_sha} to keep invalidation aligned to real change. |
| Host vs Docker paths | Host jobs: paths are directories in your checkout. Docker jobs: paths are inside the container's workspace mount. Mismatches are the most common source of "cache did nothing". |
| Path safety | Cache paths must be relative to the workspace. Absolute paths and paths containing .. are rejected. |
Planned
- Fully specified cache key computation and scoping rules.
- Cache restore/save outcomes surfaced directly in receipts and summaries.
--cache-diffworkflows to quarantine cache keys that induce mechanical divergence.