Error [can only install with an existing package-lock.json]

The npm ci command can only install with an existing package-lock.json の直し方(ロックが無い環境で npm ci)

FIX SUMMARY verified
Applies when
node:22.23.1-alpinenpm 10.9.8 (npm ci requires a committed package-lock.json; it does not generate one)running npm ci where no package-lock.json exists — commonly the lock is gitignored, never generated, or npm ci is run in a subdirectory without the lock

Verified: reproduced in node:22.23.1-alpine, then the can only install with an existing package-lock.json signature was gone after the fix (exit 0).

CI やコンテナで npm ci を実行すると、依存を1つも入れないまま次のエラーで止まることがあります。

npm error code EUSAGE
npm error
npm error The `npm ci` command can only install with an existing package-lock.json or
npm error npm-shrinkwrap.json with lockfileVersion >= 1. Run an install with npm@5 or
npm error later to generate a package-lock.json file, then try again.

終了コードは 1、エラーコードは EUSAGE です。npm 9 系までは各行が npm ERR! で始まりますが、中身は同じです。

原因は、npm ci を実行したディレクトリに package-lock.json(または npm-shrinkwrap.json)が無いことです。npm ci は依存を解決してロックを作るコマンドではなく、既存のロックからクリーンインストールする専用のコマンドなので、ロックが無いとその場で止まります。直し方は、ロックを生成してコミットしておくことです。

npm install            # package-lock.json を生成する
git add package-lock.json
git commit -m "chore: add package-lock.json"

なぜ起きるのか:npm ci はロックを作らない

npm ci(clean install)と npm install は、名前は似ていますが役割が違います。

  • npm installpackage.json を読んで依存を解決し、package-lock.json を作る/更新する。ロックが無ければ作ります。
  • npm ci … 既にある package-lock.json(または npm-shrinkwrap.json)に固定された版だけを入れる。package.json はロックとの整合の照合に使うだけで、ロックが無ければ作らず、止まります。

CI で npm ci を使うのは、ロックに固定された版がそのまま入る(=手元・CI・本番で同じ依存になる)ことを保証するためです。その入力であるロックが無い状態は、npm ci にとっては前提が満たされていないので、エラーメッセージも「まず npm install でロックを作ってから、もう一度」という案内になります。

なぜロックが無い状態が起きるのか

package-lock.jsonnpm install を1回でも回せば作られます。それが CI に無いのは、多くの場合ロックがリポジトリにコミットされていないからです。

  • .gitignorepackage-lock.json を除外しているnode_modules を無視するつもりで、テンプレートや古い慣習からロックまで無視対象に入っていることがあります。ロックはコミットするファイルです。git check-ignore package-lock.json で無視されていないか確認します。
  • 一度も npm install していないリポジトリpackage.json だけを手で書いて push した、あるいは別のパッケージマネージャ(yarn / pnpm)のロックしか無い、という状態です。yarn を使うなら Yarn 1(Classic)は yarn install --frozen-lockfile、Yarn Berry(2 以降)は yarn install --immutable、pnpm なら pnpm install --frozen-lockfilenpm ci に相当します。npm を使うなら npm のロックが要ります。
  • サブディレクトリで実行している:モノレポなどで、ロックのあるルートではなく子ディレクトリで npm ci を回していることがあります。ロックがある階層で実行します。

手元では npm install を打つたびにロックが作られて node_modules も埋まるので、ロックがコミットされていなくてもローカルのインストールは成功します。CI が npm ci を使ってはじめて、ロックがリポジトリに無いことが表面化します。

直し方:ロックを生成してコミットする

手元で npm install を1回実行し、生成された package-lock.json をコミットします。

npm install
git add package-lock.json
git commit -m "chore: add package-lock.json"

.gitignore にロックが入っていたら、その行を消してからコミットします。ロックが CI に届けば、npm ci は同期済みのロックを読んで終了コード 0 で入ります。

npm cinpm install に置き換えてもエラーは消えますが、それは npm ci を使う目的(ロックで版を固定して再現性を得る)を捨てることです(同じ理由は npm ci … in sync の直し方 に詳しく書いています)。CI では、コミット済みのロックを前提に npm ci を使うのが本筋です。

切り分け(うまくいかないとき)

  • ロックはある(コミットもされている)のに同じエラーが出る:メッセージが「in sync」を含むなら、ロックの不在ではなく package.json とのずれです。対処が違うので npm ci can only install packages when your package.json and package-lock.json are in sync の直し方 を参照してください。
  • git check-ignore package-lock.json が何か表示する:ロックが .gitignore の対象です。表示された行を削り、git add -f package-lock.json で追加してからコミットします(以降は無視されません)。
  • yarn / pnpm を使っているnpm ci は npm のロック専用です。yarn なら Yarn 1 は yarn install --frozen-lockfile、Yarn Berry は yarn install --immutable、pnpm なら pnpm install --frozen-lockfile を使い、それぞれのロック(yarn.lock / pnpm-lock.yaml)をコミットします。npm のロックと混在させないようにします。
  • メッセージが lockfileVersion >= 1 を含む:ロックはあるが npm が読める形になっていない状態です。壊れた・途中までのロックや、node_modules/.package-lock.json(インストール結果の内部ファイル)をルートのロックと取り違えていないかを確認し、プロジェクト直下で npm install を回してロックを作り直してコミットします。
  • npm ci は通ったのに、今度は別のインストールエラーになる:ロックの有無とは別の依存衝突です。peer dependency の衝突なら ERESOLVE unable to resolve dependency tree を参照してください。

検証環境

  • node:22.23.1-alpine(npm 10.9.8)、ネットワーク有り
  • 再現:package.json だけを置き(package-lock.json なし)npm ci を実行すると、The npm ci command can only install with an existing package-lock.json を出して終了コード 1
  • 修正:package.json を変えずに、生成した package-lock.json を足すと、同じ npm ci が終了コード 0・シグネチャ消滅

再現から修正までは errfix の検証ハーネスが機械的に確認しています。reproduce と fix の差は package-lock.json を足すかどうかだけで、package.json は同一です。npm ci が「ロックを作るコマンドではなく、既存ロックから入れるコマンド」であることを、ロックの有無だけで再現したものです。

検証(machine-verified)

この修正は node:22.23.1-alpine のバージョン固定コンテナ内で再現し、修正後に can only install with an existing package-lock.json のシグネチャが消えることを機械で確認しています。

verify — run-case.mjs
$ node run-case.mjs node/npm-ci-requires-lockfile
● reproduce can only install with an existing package-lock.json present ✓
● apply fix exit 0
● re-run can only install with an existing package-lock.json gone ✓
PASS verified · node:22.23.1-alpine · signature gone

確認したのは上のイメージの中だけです。別の環境で直らなかった、記述が違う、という場合は 報告してください(対象と検証イメージは件名・本文に入ります)。