Caching dependencies
Caching is one of the most effective ways to make jobs faster on CircleCI. By storing the results of expensive operations, typically dependency downloads, subsequent job runs can skip work they have already done.
Caching is most valuable with package dependency managers such as Yarn, Bundler, or pip. With a warm cache, commands like yarn install only download new or changed dependencies rather than the full set on every build.
| Caching files between different executor types can cause file permission and path errors. Affected combinations include Docker and machine executors, Linux and macOS, and CircleCI images with non-CircleCI images. Use extra care when caching across executor types. |
| The Docker images used for CircleCI jobs are automatically cached on the server infrastructure where possible. For reuse of unchanged layers within a Docker image, see Docker Layer Caching. |
How caching works
A cache stores a hierarchy of files under a key. When a job runs a restore_cache step, CircleCI looks for a stored cache matching that key and downloads it into the job environment. A save_cache step writes the specified paths to storage under the given key.
Caches are immutable on write. Once a cache is written for a specific key, it cannot be overwritten. If a subsequent job produces the same key, the existing cache is reused and no new write occurs. Reuse without overwrite is expected: a key built from a checksum of your lock file produces the same key so long as the lock file has not changed.
In the case of a cache miss, where no cache matches the key, the job still runs successfully. The job performs the full work rather than restoring from cache. Caching is always an optimization, never a requirement for correctness.
Caching is about achieving a balance between reliability and performance. Prioritize reliability: a corrupted build from stale dependencies costs more time than a fast build saves.
Storage and retention
By default, cache storage duration is set to 15 days, which is the maximum. You can set a lower retention period on the CircleCI web app under . Shorter retention reduces storage costs when caches change frequently.
To set a shorter retention period for a specific job, use the retention key in your .circleci/config.yml. Valid values range from 1d to 15d. Job-level retention overrides the organization default for caches produced by that job:
jobs:
build:
docker:
- image: cimg/node:24.16.0
retention:
caches: 7d
steps:
- checkout
- restore_cache:
keys:
- node-v1-{{ checksum "package-lock.json" }}
- run: npm install
- save_cache:
paths:
- ~/.npm
key: node-v1-{{ checksum "package-lock.json" }}
When using self-hosted runners, cache network and storage usage counts against your plan. For more information, see the Persisting Data page.
Setting up caching
CircleCI caching is manual. You configure explicitly what is cached and under what key. Automatic dependency caching is not available.
Saving cache
Add the save_cache step to a job in your .circleci/config.yml file:
jobs:
build:
docker:
- image: cimg/node:24.16.0
steps:
- save_cache:
key: my-cache-{{ checksum "package-lock.json" }}
paths:
- ~/.npm
The path is relative to the working_directory of your job. Absolute paths are also accepted. CircleCI imposes a 900-character limit on the length of a key.
Unlike persist_to_workspace, neither save_cache nor restore_cache support globbing for the paths key.
|
See the Configuration Reference for the full save_cache specification.
Restoring cache
Add the restore_cache step before the dependency install step in your job, and save_cache after it:
jobs:
build:
docker:
- image: cimg/node:24.16.0
steps:
- checkout
- restore_cache:
keys:
- my-cache-{{ checksum "package-lock.json" }}
- my-cache-
- run: npm install
- save_cache:
key: my-cache-{{ checksum "package-lock.json" }}
paths:
- ~/.npm
CircleCI restores caches in key order, using the most recent matching cache. Keys are matched by prefix: my-cache- matches any stored cache whose name begins with my-cache-. If the first key is an exact match, that cache is used. If not, CircleCI tries each subsequent key in turn.
A list of keys represents a single cache with fallback options, not multiple separate caches.
The second key in the example above (my-cache-) is a fallback that matches any cache with that prefix. A fallback key match is a partial cache restore: the restored cache may differ from your current dependencies. See Language-specific caching patterns for guidance on when this is safe for your package manager.
Restoring cache after the install step overwrites freshly installed dependencies. Always place restore_cache before and save_cache after your install command.
|
We recommend verifying that your dependency installation step succeeds before introducing caching. Caching a failed install step causes subsequent jobs to restore a broken cache. The only recovery is to update the cache key to force a fresh write.
Race conditions in workflows
Jobs in a workflow share caches. Because caches are immutable, the first job to write a key wins. Subsequent writes of the same key are discarded without error.
Example 1: Job1 and Job2 run in parallel and both write to the same key. Job3 depends on both and reads the cache. Job3 gets whichever cache was written first. The result is non-deterministic. Fix this by having Job1 and Job2 write to separate keys, or by enforcing a serial order: Job1 → Job2 → Job3.
Example 2: Job1 and Job2 run concurrently. Job2 reads the cache written by Job1. If Job2 starts before Job1 has finished saving, it may find no cache. The race condition is resolved by the explicit workflow dependency so that Job2 waits for Job1 to finish: Job1 → Job2.
Caching and open source
If your project is open source or accepts pull requests from forks:
-
PRs from the same fork repository share a cache with each other and with the main repository’s cache.
-
PRs from different fork repositories have separate caches.
-
Enabling Environment Variable sharing allows cache sharing between the original repository and all forked builds.
Designing your cache key
The cache key determines when a new cache is written and which existing cache is restored. Key design is the most consequential decision in your caching setup.
Key templates
A cache key can include templates, which are dynamic values evaluated at runtime and interpolated into the key string:
my-cache-{{ checksum "package-lock.json" }}
| Template | Description | Use for caching? |
|---|---|---|
|
Base64-encoded SHA256 hash of the file contents. Changes when the file changes. Commit this file to your repository. |
✓ Primary choice. Use your dependency manifest or lock file. |
|
The VCS branch currently being built. |
✓ Useful to scope caches per-branch when branches have divergent dependencies. |
|
OS and CPU architecture, for example |
✓ Required when caching compiled binaries that depend on OS or CPU. |
|
Any CircleCI-exported environment variable or context variable. |
⚠ Only appropriate if the variable changes at the same rate as your dependencies. |
|
Seconds since Unix epoch. Changes on every run. |
✗ Creates a new cache every run. Almost always wrong for dependency caches. |
|
The CircleCI job number. Increments on every run. |
✗ Creates a new cache every run. Almost always wrong for dependency caches. |
|
The VCS commit SHA being built. |
✗ Creates a new cache every commit, even when dependencies have not changed. |
Cache variables can also accept Parameters: v1-deps-<< parameters.varname >>.
Keys that rotate too fast
The most common and costly caching mistake is using a template that changes more frequently than your dependencies do. This causes a new cache to be written on every run, storage accumulates without limit, and no run ever benefits from a hit. Common offenders:
-
{{ epoch }}: changes every second. -
{{ .BuildNum }}: changes every build. -
{{ .Revision }}: changes every commit. -
{{ .Environment.CIRCLE_WORKFLOW_ID }}: changes every workflow run.
None of these are appropriate as the primary template in a dependency cache key. A key like my-deps-{{ epoch }} costs you storage and upload time with no performance benefit.
The correct primary template for dependency caching is {{ checksum "your-lock-file" }}, which changes only when your declared dependencies change.
Recommended key structure
For most dependency caches, the recommended structure is:
jobs:
build:
docker:
- image: cimg/node:24.16.0
steps:
- restore_cache:
keys:
- v1-deps-{{ .Branch }}-{{ checksum "package-lock.json" }}
- v1-deps-{{ .Branch }}-
- v1-deps-
This provides:
-
An exact hit when both the branch and lock file match (the common case on repeated runs).
-
A partial hit from the same branch when the lock file changes (for example, adding one dependency).
-
A partial hit from any branch as a last resort.
The v1- prefix is a manual version number. Incrementing it (v2-) invalidates all existing caches for this structure and forces a fresh write. Version incrementing is the approach described in Clearing cache.
What to cache
Cache download caches, not installed environments
The most reliable thing to cache is the download cache maintained by your package manager, which is the directory where the package manager stores downloaded packages before installing them. The download cache differs from the installed environment (node_modules, venv, vendor/bundle).
Download caches are safe to restore partially: the package manager re-resolves and installs correct versions from your manifest regardless of cached state. A stale partial restore means some packages are re-downloaded, but the install step corrects all discrepancies.
Installed environments are only safe to restore partially when every dependency is pinned to an exact version in a lock file. Without version pinning, a package manager may leave stale versions in place rather than upgrading them.
Generic tools not specific to your project, such as git or curl, belong in the Docker image, not in a cache. The CircleCI convenience images include common tooling for their respective languages. See Language Guides and Demo Projects for specifics.
What not to cache
Some paths are commonly added to caches by mistake:
-
.gitdirectory: changes on every commit and provides no restoration value. This directory is often swept in when a job caches the entire project root. -
Project root (
.or/home/circleci/project): includes build output, test artifacts, temporary files, and.git. Cache size grows unpredictably and restore time increases with no benefit. -
Build output directories (
target/,dist/,build/): compiled artifacts that are fast to regenerate do not benefit from caching. See Caching expensive compilation steps for cases where compilation caching is worthwhile. -
Installed environments without a lock file: a
venv,node_modules, orvendor/bundlerestored from a partial cache without version-pinned dependencies can contain wrong package versions. See Language-specific caching patterns.
Cache size and restore time
CircleCI does not impose a cache size limit, but larger caches take longer to restore. The relevant measure is whether the time to download and expand a cache is less than the time to install dependencies from scratch.
For most dependency caches (under 300 MB) restore time is well within that threshold. For large caches, for example, Conda environments, large Gradle repositories, binary assets, it is worth measuring the following:
-
If your P50 restore time exceeds 15 seconds, time a cold install for comparison.
-
If the cache restore is comparable to or slower than a cold install, consider alternatives: a custom Docker image with dependencies pre-installed, or splitting one large cache into separate per-language caches.
The expansion step (decompression and file extraction) is typically the bottleneck, not download. Large caches have a proportionally higher restore time penalty.
Using a lock file
Lock files are the preferred checksum source for cache keys, for example:
-
Gemfile.lock -
package-lock.json -
yarn.lock -
Pipfile.lock -
poetry.lock -
uv.lock
Lock files capture the exact resolved dependency tree. The cache key therefore changes only when your dependencies change, not when an unpinned version range resolves differently.
For projects without a lock file, you can generate a deterministic checksum from the installed directory listing:
ls -laR your-deps-dir > deps_checksum
Then reference it with {{ checksum "deps_checksum" }}. Using a checksum of the directory listing produces a more specific key than using a checksum of an unpinned requirements file.
Language-specific caching patterns
Whether partial cache restores (using fallback keys) are safe depends on how each package manager handles an existing, potentially out-of-date dependency tree. The sections below document the recommended pattern and safety characteristics for each common package manager.
Bundler (Ruby)
Safe to use partial cache restoration? Yes, with a clean step.
Bundler can reference system gems that are not explicitly specified, making partial restores non-deterministic without intervention. A bundle clean --force step after bundle install corrects any stale gems from the partial restore before the cache is saved.
jobs:
build:
docker:
- image: cimg/ruby:3.3.11
steps:
- checkout
- restore_cache:
keys:
- v1-gem-cache-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }}
- v1-gem-cache-{{ arch }}-{{ .Branch }}-
- v1-gem-cache-{{ arch }}-
- run: bundle config set --local path vendor/bundle
- run: bundle install
- run: bundle clean --force
- save_cache:
paths:
- vendor/bundle
key: v1-gem-cache-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }}
Gradle (Java)
Safe to use partial cache restoration? Yes.
Gradle repositories are designed to be centralized and additive. Libraries are fetched into the cache and resolved from it; partial restores do not affect which libraries end up on classpaths.
jobs:
build:
docker:
- image: cimg/openjdk:21.0.11
steps:
- checkout
- restore_cache:
keys:
- gradle-repo-v1-{{ .Branch }}-{{ checksum "build.gradle" }}
- gradle-repo-v1-{{ .Branch }}-
- gradle-repo-v1-
- save_cache:
paths:
- ~/.gradle/caches
- ~/.gradle/wrapper
key: gradle-repo-v1-{{ .Branch }}-{{ checksum "build.gradle" }}
If you use Gradle dependency locking, replace build.gradle with the path to your lock file (for example, gradle/dependency-locks/compileClasspath.lockfile). Using a lock file produces a more precise cache key.
|
Maven (Java) and Leiningen (Clojure)
Safe to use partial cache restoration? Yes.
Maven repositories are designed to be centralized and additive. Leiningen uses Maven under the hood and behaves the same way.
jobs:
build:
docker:
- image: cimg/openjdk:21.0.11
steps:
- checkout
- restore_cache:
keys:
- maven-repo-v1-{{ .Branch }}-{{ checksum "pom.xml" }}
- maven-repo-v1-{{ .Branch }}-
- maven-repo-v1-
- save_cache:
paths:
- ~/.m2/repository
key: maven-repo-v1-{{ .Branch }}-{{ checksum "pom.xml" }}
npm (Node)
Safe to use partial cache restoration? Yes, with npm 5+.
Cache the npm download cache at ~/.npm, not node_modules directly. The ~/.npm path is the correct location regardless of npm version and works with both npm install and npm ci.
Caching node_modules directly is unreliable. It breaks when Node or npm versions change between the cache write and the restore. Partial restores can also leave orphaned packages that affect runtime behaviour. The ~/.npm download cache avoids these problems: npm resolves and installs the correct versions from your lock file, using the cached downloads to skip the network fetch.
jobs:
build:
docker:
- image: cimg/node:24.16.0
steps:
- checkout
- restore_cache:
keys:
- node-v1-{{ .Branch }}-{{ checksum "package-lock.json" }}
- node-v1-{{ .Branch }}-
- node-v1-
- run: npm install
- save_cache:
paths:
- ~/.npm
key: node-v1-{{ .Branch }}-{{ checksum "package-lock.json" }}
|
If your job uses |
Pip (Python)
Safe to use partial cache restoration? Depends on what you cache.
-
Download cache (
~/.cache/pip): pip stores downloaded wheels here before installing them. Partial restoration is safe: pip resolves and installs correct versions from your dependency file regardless of what wheels are present in the cache. -
Installed environment (a virtual environment or
site-packagesdirectory): partial restoration is only safe with a lock file that pins every dependency to an exact version. Without pinning, pip may leave stale versions in place rather than upgrading them.
The sections below follow this distinction. Bare pip and uv cache the download cache and use fallback keys without risk. Pipenv and Poetry cache the installed environment and rely on their lock files to make partial restores deterministic.
Pip with requirements.txt
Cache the download cache at ~/.cache/pip. Partial restoration is safe.
jobs:
build:
docker:
- image: cimg/python:3.12.13
steps:
- checkout
- restore_cache:
keys:
- pip-cache-v1-{{ .Branch }}-{{ checksum "requirements.txt" }}
- pip-cache-v1-{{ .Branch }}-
- pip-cache-v1-
- run: pip install -r requirements.txt
- save_cache:
paths:
- ~/.cache/pip
key: pip-cache-v1-{{ .Branch }}-{{ checksum "requirements.txt" }}
Pipenv
Pipenv generates a Pipfile.lock that pins every dependency to an exact version. Partial restoration of the installed virtual environment is safe, because the lock file ensures pip installs correct versions even on top of a stale partial restore.
Setting PIPENV_VENV_IN_PROJECT=1 causes Pipenv to create the virtual environment at .venv in the project directory, giving you a stable, predictable path to cache.
jobs:
build:
docker:
- image: cimg/python:3.12.13
steps:
- checkout
- restore_cache:
keys:
- pip-packages-v1-{{ .Branch }}-{{ checksum "Pipfile.lock" }}
- pip-packages-v1-{{ .Branch }}-
- pip-packages-v1-
- run:
command: pipenv install
environment:
PIPENV_VENV_IN_PROJECT: "1"
- save_cache:
paths:
- .venv
key: pip-packages-v1-{{ .Branch }}-{{ checksum "Pipfile.lock" }}
Poetry
Poetry generates a poetry.lock file that pins all dependencies to specific versions. As with Pipenv, the lock file makes partial restoration of the installed virtual environments directory safe.
jobs:
build:
docker:
- image: cimg/python:3.12.13
steps:
- checkout
- restore_cache:
keys:
- poetry-v1-{{ .Branch }}-{{ checksum "poetry.lock" }}
- poetry-v1-{{ .Branch }}-
- poetry-v1-
- run: poetry install --no-interaction
- save_cache:
paths:
- ~/.cache/pypoetry/virtualenvs
key: poetry-v1-{{ .Branch }}-{{ checksum "poetry.lock" }}
The uv package installer
uv maintains its own download cache at ~/.cache/uv. Like bare pip, the uv cache is a download cache rather than an installed environment, so partial restoration is safe.
jobs:
build:
docker:
- image: cimg/python:3.12.13
steps:
- checkout
- restore_cache:
keys:
- uv-cache-v1-{{ .Branch }}-{{ checksum "uv.lock" }}
- uv-cache-v1-{{ .Branch }}-
- uv-cache-v1-
- run: uv sync --frozen
- save_cache:
paths:
- ~/.cache/uv
key: uv-cache-v1-{{ .Branch }}-{{ checksum "uv.lock" }}
|
Package installation with |
Yarn (Node)
Safe to use partial cache restoration? Yes.
Yarn has always used a lock file, making partial restoration safe.
| Yarn 2.x supports Zero Installs. If you are using Zero Installs, no caching configuration is needed in CircleCI. |
Yarn 1.x:
jobs:
build:
docker:
- image: cimg/node:24.16.0
steps:
- checkout
- restore_cache:
keys:
- yarn-packages-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}
- yarn-packages-v1-{{ .Branch }}-
- yarn-packages-v1-
- run: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
- save_cache:
paths:
- ~/.cache/yarn
key: yarn-packages-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}
Two flags are important here:
-
--frozen-lockfileensures the lockfile is not modified during install, keeping the checksum stable for thesave_cachekey. -
--cache-folder ~/.cache/yarnmakes the cache location explicit. The default location is OS-dependent; setting it explicitly ensures thepathsinsave_cachealways matches where Yarn writes.
Yarn 2.x without Zero Installs:
jobs:
build:
docker:
- image: cimg/node:24.16.0
steps:
- checkout
- restore_cache:
keys:
- yarn-packages-v1-{{ checksum "yarn.lock" }}
- run: yarn install --immutable
- save_cache:
paths:
- .yarn/cache
- .yarn/unplugged
key: yarn-packages-v1-{{ checksum "yarn.lock" }}
Caching strategy tradeoffs
The right caching approach depends on your package manager, project structure, and how frequently dependencies change. The following patterns cover the most common decision points: when to use fallback keys, how to handle multiple dependency types, and when caching applies beyond dependency downloads.
When partial caching helps vs. hurts
The following package managers use additive, centralized repositories where partial caching with fallback keys is the default recommendation:
-
Gradle
-
Maven
-
npm
-
Yarn
These managers install on top of a partially restored cache without issues. Getting dependencies from a cached state is faster than a full cold install.
For installed environments without exact version pinning, partial caching can produce incorrect dependency states. In these cases a cache miss is preferable to a partial hit. Use a single exact-match key with no fallbacks, or use a lock-file-based tool such as Pipenv or Poetry.
Broad fallback keys can cause one branch’s cache to restore on another, producing unexpected dependency states. For example, a project upgrading from Node 18 to Node 20 on a feature branch may restore a Node 18 cache if the fallback key is broad enough. If you see unexpected dependency behavior on a branch, clear the cache by incrementing the version prefix in your key.
Multiple caches for different languages
For projects with multiple dependency types, for example, both npm and pip, use separate cache keys for each. Each cache is smaller, so a miss on one type does not force a full re-download of all types.
Split caches at the package manager boundary (npm, pip, Bundler). Each manager has its own storage format and upgrade semantics, so a miss on one type would otherwise invalidate all of them.
Caching expensive compilation steps
Caching also applies to expensive compilation or asset generation steps, not just dependency downloads. Scala and Elixir compilation and Rails frontend asset pre-compilation are common examples where caching a build step output saves meaningful time.
For compilation caches, use a key that reflects what determines the output, for example, a checksum of source files or a version string, rather than a dependency manifest.
Do not cache every step. Cache only where restore time is less than the time to regenerate the output.
Optimizing your cache
Once you have your caching set up, the next step is to make the caching strategy more efficient. The patterns outlined in this section will assist you to reduce storage costs, eliminate redundant uploads, and ensure caches are actually being hit.
Split cache keys by directory
Avoid grouping unrelated directories under a single cache key. If any one directory changes, the entire cache is invalidated and all directories are re-uploaded and re-restored. Separate keys mean only the changed cache is refreshed.
jobs:
build:
docker:
- image: cimg/node:24.16.0
steps:
# Avoid: unrelated directories tied to one key
- save_cache:
paths:
- /mnt/ramdisk/node_modules
- /mnt/ramdisk/.cache/yarn
- /mnt/ramdisk/apps/a/node_modules
- /mnt/ramdisk/apps/b/node_modules
key: v1-node-{{ checksum "package.json" }}
Combine jobs where duplicate steps add no value
If two parallel jobs perform the same setup steps, combining them into one job removes duplicated overhead without changing total workflow duration. Common duplicate steps include checkout, cache restore, build, and save cache.
For example, if lint (20s) and code-coverage (30s) both perform an identical build step, combining them saves one full set of cache restore and build steps.
Order jobs to avoid duplicate cache writes
If multiple parallel jobs write to the same cache key, only the first to complete saves a cache (caches are immutable). The remaining jobs each upload a cache that CircleCI discards, wasting upload time. Reordering so that one upstream job produces the cache and downstream jobs consume it avoids redundant uploads.
Prune unnecessary files before saving
Dependency managers sometimes leave unnecessary files in their directories, for example, documentation, type stubs, test fixtures. Tools like node-prune can remove these before the save_cache step, reducing cache size and expansion time on restore.
If you prune files from a cached directory, ensure those files are not included in the checksum used for the cache key. A key mismatch between save and restore causes a miss on the next run.
Check cache is being restored as well as saved
A cache that saves but never restores is a cost with no benefit. If you find a cache is not being restored, see this support article for troubleshooting steps.
Identify high-cost, low-value cache keys
If cache storage costs are higher than expected, look for keys with high write volume but low or zero hit counts. The common cause is a key template that rotates on every run: {{ epoch }}, {{ .BuildNum }}, {{ .Revision }}, or {{ .Environment.CIRCLE_WORKFLOW_ID }}. These write a new cache on every run and are never reused.
Switch to {{ checksum "lockfile" }}, which writes a new cache only when declared dependencies change.
Managing caches
CircleCI does not provide a way to delete individual caches directly. Use key rotation to force a fresh cache write. You can inspect your cache size and storage usage from the jobs page.
Clearing cache
Caches cannot be deleted directly. To force a fresh write, increment the version prefix in your cache key. Update the prefix in both restore_cache and save_cache steps. If only restore_cache is updated, the old key is never matched but the new cache is never written either.
jobs:
build:
docker:
- image: cimg/node:24.16.0
steps:
- checkout
# Increment v1 to v2 in both restore_cache and save_cache
- restore_cache:
keys:
- v2-deps-{{ .Branch }}-{{ checksum "package-lock.json" }}
- run: npm install
- save_cache:
key: v2-deps-{{ .Branch }}-{{ checksum "package-lock.json" }}
paths:
- ~/.npm
Do this when:
-
You change your dependency manager version (for example, npm 4 to 5).
-
You change your language runtime version (for example, Ruby 2.3 to 2.4).
-
You remove dependencies and want to clear the stale packages from cached state.
-
A cache is corrupted and causing build failures.
Incrementing the version creates a new cache going forward but does not delete the old one. Old caches expire after your configured retention period. If you rebase commits that predate the key change, those jobs may write under the old key again.
Use only [a-zA-Z0-9-_] characters in cache key prefixes. Special characters (:, ?, &, =, /, #) may cause issues with your build.
|
Viewing cache size and storage usage
The restore_cache step output on the CircleCI jobs page shows the restored cache size. For fleet-wide storage usage and overage cost calculations, see the Persisting Data page.
Advanced patterns
The following pattern covers monorepos, where a single root lock file checksum may miss dependency changes across sub-packages.
Caching in monorepos
For monorepos with multiple lock files in different packages, using a checksum of a single root package-lock.json may miss dependency changes in sub-packages. One approach is to concatenate all lock files and checksum the combined file.
Add a custom command to your config:
commands:
create_concatenated_package_lock:
description: "Concatenate all package-lock.json files recognized by lerna.js into a single file for use as a cache key checksum."
parameters:
filename:
type: string
steps:
- run:
name: Combine package-lock.json files
command: npx lerna la -a | awk -F packages '{printf "\"packages%s/package-lock.json\" ", $2}' | xargs cat > << parameters.filename >>
Then use the concatenated file as an additional checksum:
jobs:
build:
docker:
- image: cimg/node:24.16.0
steps:
- checkout
- create_concatenated_package_lock:
filename: combined-package-lock.txt
- restore_cache:
keys:
- v3-deps-{{ checksum "package-lock.json" }}-{{ checksum "combined-package-lock.txt" }}
- v3-deps-