npm install or npm ci taking a large slice of every run

A slow npm install almost always means one of three things: the npm cache is never restored, the same install runs more than once across jobs in the workflow, or several jobs each pay for an identical setup preamble. Check the setup-node step, the job graph, and the workflow's own step timings in that order before changing anything.

static · checkable from the repo
How StarSling works

AI agents find the cause

StarSling agents run this diagnosis against your workflows and run history, identify which cause is yours, and open the fix as a reviewable PR.

Symptoms

In a GitHub Actions run, npm install or npm ci resolves the dependency tree and writes every package to node_modules on a runner that started empty seconds earlier. If nothing restored a prior run's npm cache, that resolution refetches every tarball from the registry instead of reading it from disk, and the install pays full network cost every single time. That cost then multiplies in two common ways: a workflow with several jobs, lint, test, build, e2e, that each independently check out the repo and reinstall the identical dependency tree, or a workflow whose jobs each carry their own copy of the same checkout-and-setup preamble instead of sharing one. The order below follows how often each cause actually accounts for the time: no cache first, because it is the single biggest lever and the easiest to confirm from the setup-node step alone; duplicate installs next, because splitting work across jobs is normal and only becomes waste when nothing hands the result forward; then duplicated setup, which wastes less per job but adds up across a wide job graph.

  • The install step alone takes a minute or more on almost every run, even when the lockfile has not changed since the last commit.

  • The workflow has several jobs, lint, test, build, e2e, and each one has its own npm install or npm ci step near the top.

  • The setup-node step's log reads "npm cache is not found" on runs where the lockfile is identical to the previous run.

  • Total workflow time barely moves even after the actual test or build commands got faster.

  • Two or more jobs in the same workflow file open with the same sequence of checkout, setup-node, and install steps.

How to diagnose it

  1. Open the workflow file and list every job: yq '.jobs | keys' .github/workflows/ci.yml. Note which jobs contain an install step.

  2. For each install step, check the preceding setup-node block for cache: npm. Its absence on any job is cause 1 for that job.

  3. Open one recent run in the Actions UI and read the setup-node or cache step's log line: a cache hit or miss settles cause 1 versus cause 2 without guessing.

  4. Count install steps across the whole workflow file and check for actions/upload-artifact / actions/download-artifact between them; more than one install with no handoff points at cause 3.

  5. Compare the first four steps of every job with yq '.jobs | to_entries[] | {name: .key, steps: .value.steps[0:4]}'; identical sequences across jobs point at cause 4.

  6. If the install step still dominates total job time after ruling out the above, compute the ratio of setup time to total job time directly from the run's step timings in the Actions UI to confirm how much of the run install actually accounts for.

Likely causes

Ordered by how often each one turns out to be the answer. Confirm a cause with its check before you change anything.

  1. npm's own cache is never restored

    actions/setup-node has a built-in cache input that wraps actions/cache around npm's package cache, keyed on the lockfile, but it only activates when a workflow sets it. Without it, npm ci resolves against the lockfile correctly but has nothing local to read from, so every package is refetched over the network on every run. Separate downloads a job does outside npm's own cache, browser binaries, SDKs, are invisible to that flag and need their own cache step, and an install step for a tool the job never runs is pure waste layered on top of a cold cache.

    Confirm it

    grep -A5 'actions/setup-node' .github/workflows/*.yml and check whether the block includes a cache: npm (or cache: 'npm') line; if it is absent, this is the cause.

    Fix: npm install caching
  2. The dependency cache exists but never hits

    A workflow can have an actions/cache or setup-node cache step in place and still see no benefit if the cache key never matches: a key that is not derived from the lockfile hash, a key that includes something that changes every run (a timestamp, a commit SHA), or a restore-keys fallback that is too broad to be useful. In that state the workflow looks cached but behaves exactly like an uncached install.

    Confirm it

    Open a recent successful run's setup-node or cache step in the Actions UI and read the log line directly. Both actions print "Cache restored from key" on a hit, but they word the miss differently: actions/cache prints "Cache not found for input keys", while actions/setup-node prints "npm cache is not found" (with pnpm or yarn in place of npm for those managers). Match the string to the action the job actually uses, then check that the key in the workflow YAML includes hashFiles('package-lock.json') or an equivalent lockfile hash.

    Fix: Dependency caching
  3. Multiple jobs each install the same dependencies from scratch

    Every job in a GitHub Actions workflow gets its own fresh runner with nothing carried over from a sibling job, so a lint job, a test job, and a build job that each check out the repo and run npm install independently resolve and write the same dependency tree that many times. Nothing about running jobs in parallel requires this: it only happens when no job hands its installed dependencies to the others.

    Confirm it

    grep -rn 'actions/upload-artifact\|actions/download-artifact' .github/workflows/ combined with counting install steps per workflow file; a workflow with two or more install steps and zero artifact-handoff matches is reinstalling from scratch in every job.

    Fix: Duplicate dependency installs
  4. The same checkout-and-install preamble is copy-pasted into every job

    When several jobs in a workflow each carry their own copy of checkout, setup-node, and install steps instead of sharing a composite action or reusable workflow, the runner pays that preamble's cost once per job, and a version bump or cache-key fix made in one copy silently does not apply to the others until someone remembers to update every copy.

    Confirm it

    yq '.jobs | to_entries[] | {name: .key, steps: .value.steps[0:4]}' .github/workflows/ci.yml and compare the opening step sequence across jobs; identical sequences with no shared composite action behind them confirm this cause.

    Fix: Duplicated job setup

Hand it to an agent

Hand this prompt to your coding agent (Claude Code, Cursor, and the like) to run this diagnosis against your repository and report which cause it found.

Prompt for your coding agent
Diagnose why npm install or npm ci is slow in this repository's GitHub Actions workflows. Do not change anything yet; report findings first.

1. List every workflow under .github/workflows/ and every job in each, noting which jobs run npm install or npm ci.
2. For each such job, check whether its actions/setup-node step sets `cache: npm`. If it does not, that is likely cause 1.
3. If setup-node's cache is enabled, open the most recent successful run's log for that step and check whether it reports a cache restore or a cache miss. A miss despite the flag being set points at cause 2 (a bad or non-lockfile-derived cache key).
4. Count install steps across each workflow file. If more than one job installs the same dependencies with no actions/upload-artifact and actions/download-artifact handoff between them, that is cause 3.
5. Compare the opening steps (checkout, setup, install) across jobs in the same workflow. Identical sequences repeated with no shared composite action is cause 4.

Report which cause (or causes) you found, with the file and job names, before proposing or making any change.

Go further

Find the cause, fix them all, or keep them fixed.

You have the prompt that works out which cause applies. Here is how much further you can take it, each step doing more for you than the last.

  1. Find the cause

    Copy the diagnosis prompt above

    Hand it to your coding agent to confirm which cause applies in your repo. It reports back what it found and changes nothing.

  2. Fix them all, once

    Install the ci-speedup skill

    One prompt audits your whole repo against all 73 ci-speedup patterns (these 10 plus 63 more) and hands your agent every fix at once. Open source, MIT, runs locally.

  3. Keep it fixed, forever

    Install the StarSling GitHub App

    Connect GitHub and the fixes stay applied as your CI evolves, with agents that keep inspecting your workflows and opening optimization PRs you review.

Sources

1actions/setup-node: caching global packages data (opens in new tab)

2GitHub Actions: caching dependencies to speed up workflows (opens in new tab)

3GitHub Actions: storing workflow data as artifacts (opens in new tab)

4npm docs: npm ci (opens in new tab)

Last updated 2026-08-31