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 install…package.jsonを読んで依存を解決し、package-lock.jsonを作る/更新する。ロックが無ければ作ります。npm ci… 既にあるpackage-lock.json(またはnpm-shrinkwrap.json)に固定された版だけを入れる。package.jsonはロックとの整合の照合に使うだけで、ロックが無ければ作らず、止まります。
CI で npm ci を使うのは、ロックに固定された版がそのまま入る(=手元・CI・本番で同じ依存になる)ことを保証するためです。その入力であるロックが無い状態は、npm ci にとっては前提が満たされていないので、エラーメッセージも「まず npm install でロックを作ってから、もう一度」という案内になります。
なぜロックが無い状態が起きるのか
package-lock.json は npm install を1回でも回せば作られます。それが CI に無いのは、多くの場合ロックがリポジトリにコミットされていないからです。
.gitignoreがpackage-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-lockfileがnpm 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 ci を npm 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 が「ロックを作るコマンドではなく、既存ロックから入れるコマンド」であることを、ロックの有無だけで再現したものです。