GitHub Actions · monorepos

When Actions can’t find package-lock.json.

In a monorepo, two paths must be configured independently: the lockfile that setup-node hashes for caching and the directory where npm ci actually runs.

Short version: checkout first, point cache-dependency-path at the package lock, then set working-directory for the install command. One setting does not replace the other.

The two controls solve different problems.

cache-dependency-path

Tells setup-node which dependency file to hash when package-manager caching is enabled. It accepts subdirectory paths, wildcards, and multiple paths.

working-directory

Tells a run step where to execute. It does not change where a preceding uses: actions/setup-node step searches.

The setup action caches global package-manager data rather than node_modules. A restored cache therefore does not eliminate the clean install step.

A single-package monorepo job.

For a web package at apps/web, keep the paths aligned explicitly:

jobs:
  test-web:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    defaults:
      run:
        working-directory: apps/web
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version-file: apps/web/.nvmrc
          cache: npm
          cache-dependency-path: apps/web/package-lock.json
      - run: npm ci
      - run: npm test

defaults.run.working-directory applies to the two run steps. It does not apply to either action step, which is why the setup inputs still use repository-relative paths.

Multiple package locks.

If several packages have independent lockfiles, the caching input can hash a list or wildcard:

- uses: actions/setup-node@v7
  with:
    node-version: 22
    cache: npm
    cache-dependency-path: |
      apps/*/package-lock.json
      packages/*/package-lock.json

That only configures the cache key. Each install still needs an intentional package directory, usually through separate jobs, a matrix, or per-step working-directory.

Check these in order.

  1. Confirm checkout occurs before setup and install; the target directory must exist on the runner.
  2. Confirm the lockfile is committed at the exact repository-relative path and matches case.
  3. Set cache: npm and the corresponding cache-dependency-path.
  4. Set the install step's working-directory or a job-level defaults.run.working-directory.
  5. Run npm ci; treat a cache miss as a normal cold start, not automatically as a failure.

Three tempting non-fixes.

Boundary: a cache hit proves reusable package-manager data was restored for a matching key. It does not prove dependencies, install scripts, tests, credentials, or hosted services will succeed.

Primary references.

Still stuck on one workflow?

CI Rescue provides a focused diagnosis, reviewable patch, and verification report for one failing GitHub Actions job path. Public intake must be sanitized; no credentials or production access are accepted.

Request a $49 rescue →