この記事は、npm ciが依存関係の不一致を理由に停止したとき、package.jsonとpackage-lock.jsonをむやみに書き換えず、原因を切り分けて再現可能な状態へ戻すための手順書です。まずエラー全文、実行したnpm/Node.jsの版、作業ディレクトリ、package manager、CIで使う設定を記録してください。表示されたコードがEUSAGEでも、その文字列だけから原因を決めつけず、エラー本文が指すmanifest、lockfile、workspace、設定を確認します。peer dependencyの解決失敗を示すERESOLVE、lockfile自体がない場合、認証・通信エラーは別の分岐です。
最初に結論:npm ciはlockfileを都合よく更新して先へ進むコマンドではありません。package-lock.jsonまたはnpm-shrinkwrap.jsonが必要で、package.jsonとの依存定義が合わなければエラーで止まり、lockfileを更新しません。また、既存node_modulesを削除してからプロジェクト全体をインストールするため、作業ツリーやCI環境で実行する前に対象ディレクトリを確かめます。診断中に一時的にnpm installへ置き換えるとロック済み依存の変更が起きうるため、まず読むだけの確認を行い、変更が必要と分かった後に限りlockfileを再生成します。
目次
最初に記録する情報
失敗した端末とCIの両方で、作業ディレクトリ、Node.jsの版、npmの版、実際のコマンド、終了コード、エラー全文を記録します。monorepoやworkspaceなら、rootと対象workspaceのどこでコマンドを実行したかも必要です。エラー全文は先頭のcodeだけでなく、どのpackage名・version・依存定義・lockfile entryをnpmが示しているかを残します。ログや設定を共有するときは、private registryの認証token、環境変数、.npmrcの認証行、社内URLなどを伏せてください。npm configを丸ごと貼らず、必要な設定名と秘密を含まない値だけを共有します。
次にpackage managerを確認します。プロジェクトがnpmを使う前提か、pnpmやYarnのlockfileを持つ構成かを確認し、CIの設定ファイルやREADME、packageManager欄など、プロジェクトが明示する契約を優先します。npm ciを実行する場所にpackage.jsonとpackage-lock.json(またはnpm-shrinkwrap.json)が存在するかを確認します。workspaceではlockfileがrootに置かれている場合があるため、サブディレクトリ単独のファイル有無だけで「lockfileがない」と判断しません。
package.jsonとlockfileの差分を読む
まずGit上の変更差分を見て、package.jsonとlockfileが同じ変更単位で更新されたかを確認します。依存を追加・削除・version range変更したのにlockfileが更新されていないなら、manifestの変更漏れが候補です。反対に、lockfileだけが大きく変わっているときは、別npm版による形式更新、別のnpm設定、別ブランチのlockfile混入、依存解決の更新が起きた可能性があります。差分の量が多いから必ず誤りとは限りませんが、意図したpackage変更と関係のない大量のversion変更が含まれていないかをレビューします。
lockfileの最上位にあるname、version、lockfileVersionがpackage.jsonや実行環境とどう対応するかを確認します。npmのlockfile形式はnpm CLIの世代によって変わることがあるため、lockfileVersionの違いだけを単独の故障原因と断定しません。バージョン差を調べるときは、誰がどのnpmで生成したのか、CIがどのnpmで読んでいるか、差分に依存解決の変更も含まれているかを併せて見ます。古いlockfileの取り扱いもnpm版によって異なるので、「数字を現在の最新形式に合わせれば直る」と機械的に書き換えるのは避けます。
package.jsonのdependencies、devDependencies、optionalDependencies、peerDependencies、overrides、workspacesなど、該当する依存定義をlockfileのroot情報と照合します。package-lock.jsonは各packageの解決済みversionや依存関係を記録しますが、依存rangeを受け入れる基準はpackage.json側にあります。manifest側でrangeを変更している場合、既存のresolved versionが新rangeを満たすか、追加・削除されたdependencyがlockfileに反映されているかを確認します。workspace構成では対象importerの場所と依存定義を照合し、別workspaceの変更をroot lockの問題と混同しないようにします。
npm設定と生成条件のずれ
lockfileを作った側とnpm ciを実行する側で、依存ツリーの形を変える設定が一致しているかを確かめます。npm公式資料は、lockfile作成時に--legacy-peer-depsや--install-linksのようなtree-shaping flagsを使った場合、npm ciでも同じ条件を渡さないとエラーになる場合があると案内しています。これらの設定が必要なら、既存のプロジェクト契約とレビューに従って、共有可能なproject .npmrcやCI commandへ記録します。個人環境のuser-level設定だけで偶然通る状態を完成扱いにしないでください。
設定確認では、プロジェクトの.npmrc、CI workflow、package scripts、環境変数、実行コマンドを並べ、実際に有効な値を比較します。.npmrcにはregistry token等の秘密が入ることがあるため、内容を公開ログへコピーしないこと。設定の差分は、認証に関係しないflag名と安全な値だけを表に記録します。legacy-peer-depsはpeerDependenciesの扱いを変える設定なので、ただエラーを消す目的で追加せず、プロジェクトがその依存ツリーを意図している根拠があるかを確認します。設定を変える場合は、ローカルだけでなくCIにも同じ条件が適用されるかまで追跡します。
エラーの種類も切り分けます。npm ciがpackage manifestとlockfileの不一致を示す場合は、依存定義とロック内容を確認します。ERESOLVEなどpeer dependencyの競合が明記される場合は、依存間のpeer rangeとnpmの解決条件を別途確認し、EUSAGEと同じ修復として扱いません。ENOENTは必要ファイルや実行場所、認証・ネットワークのエラーはregistry到達性やcredentialの範囲を調べます。エラーcodeはnpm版や失敗箇所に影響されるため、記事の例と実際のログの説明が違うときは、コード名よりエラー本文と対象ファイルを優先します。
原因別の安全な直し方
意図したdependency変更があるのにlockfileだけが古いと確認できた場合は、対象branchを切り、変更前のpackage.json・lockfile・npm設定を保存してから、プロジェクトで定めたnpm版と共有設定を使ってlockfileを更新します。npm installにはpackage-lock-only optionがあり、package-lock.jsonだけを更新対象にするモードを選べます。ただし、それは「現在のlockをそのまま維持する」意味ではありません。npm installはpackage.jsonのrangeと既存lockを比較し、rangeが一致しない場合は条件を満たす新しいversionを解決してlockを更新することがあります。したがって、実行前に変更意図を決め、実行後はpackage-lock差分を1件ずつレビューします。
依存定義そのものを変える意図がなかった場合は、安易にnpm installを走らせてlockfileを追従させず、同じcommit・同じ作業ツリーのpackage.jsonとlockfileを復元できるか確認します。別branchから片方だけを持ち込んだ、merge conflictを誤って解決した、root/workspaceを取り違えた、という経緯が見つかったら、その原因を修正して対になるファイルを同じ変更単位へ戻します。lockfileの一部だけを手編集して辻褄を合わせたり、lockfileを削除してから再生成したりすると、意図しないdependency upgradeや再解決範囲の拡大を招く可能性があるため、必要な根拠と差分レビューなしに行いません。
npm版の違いが原因候補の場合は、CIが実際に使うNode/npm版を確認し、ローカルとの違いを再現します。プロジェクトがnpm版を固定しているならその版でlockを生成・検証し、固定していないなら、まず既存のCIと開発環境のどちらを契約として採用するかプロジェクトの保守者が決めます。新しいnpmへ上げること自体はEUSAGEの一般的な解決策ではありません。版を変えればlockfile形式や解決結果も変わり得るので、package-lockの広い差分が意図したものかを別に判断します。
修復後の検証
修正後は、意図したmanifestとlockfileだけが変わったか、予期しないdependency version更新がないか、秘密を含む設定ファイルが差分に混ざっていないかを確認します。lockfileを更新した場合、diffでname/version、依存range、resolved package、workspace importerなどを確認し、作業前に決めた変更意図に一致することをレビューします。.npmrcを追加・変更したなら、内容に認証情報が含まれないこと、共有範囲と値が適切であることを別途チェックします。
次に、cleanな一時worktreeまたは隔離されたCI jobで、同じNode/npm版、同じworking directory、同じtree-shaping flagsを用いてnpm ciを実行します。npm ciは既存node_modulesを消すため、他の作業があるディレクトリで不用意に実行せず、先に対象pathと変更を保全します。clean installの成功後に、package.json、lockfile、共有設定が意図せず書き換わっていないか再確認し、プロジェクトの通常テスト・buildを走らせます。ローカルだけの成功ではCIの再現性を証明できないため、CIの同一jobでも確認します。
検証結果には、実際に試した環境・Node版・npm版・コマンド・設定flag・対象branch・成功または失敗を記録します。環境を実行していない場合は「手順案を確認した」または「差分をレビューした」とし、npm ciが実際に成功したとは書きません。認証を必要とするprivate registryで検証できない場合は、その事実と、credentialを開示せずに実行できる担当者向け再現手順を残します。これにより、確認済みの仕様と、個別プロジェクトでまだ実行していない作業を混同せずに引き継げます。
再発を防ぐためのチェック表
PRを作る前に、次の項目をチェックします。package.jsonのdependency変更があるなら対応するlockfileも同じPRに含まれているか。変更差分がその依存追加・削除・range変更と説明できるか。workspace rootと実行場所が合っているか。npm版とtree-shaping flagsがCIと生成側で一致するか。npm ciが古いlockを暗黙に更新することを期待していないか。これらを満たすと、エラーのたびにlockfile全体を再生成するのではなく、具体的な原因に合わせて変更範囲を限定できます。
CIでは、install commandをnpm installに置き換えてエラーを隠すのではなく、プロジェクトが採用するlockfileに対してfrozen installの契約を保ちます。CIの起動ディレクトリがworkspace rootか、npm cache利用や環境変数で条件が変わっていないかも確認します。チームが特定flagを必要とするなら、ローカル開発者の記憶や個人設定ではなく、リポジトリから共有される設定とCI workflowに反映し、レビューで意図を説明します。これにより、新しくcheckoutした人とCIが異なる前提でlockfileを扱う状態を避けられます。
どの分岐で止めるか
package.jsonの変更が意図的か分からない、lockfileの大規模差分に説明がつかない、private registry credentialsが必要だが権限がない、CIで使用されるnpm版が特定できない、といった場合は、推測でlockを再生成せず担当者へ確認します。ERESOLVEがpeer dependencyの互換性問題を示している場合は、競合するpackageとrangeを特定してから選択肢を検討します。legacy-peer-depsを加えて通すだけでは、peer contractを無視した依存ツリーになりうるため、採用するならなぜ必要なのか、将来外せるか、CIと開発環境の両方で同条件かを記録します。
また、lockfileの差分をレビューする人が依存の意味を判断できないほど大きい場合は、変更を小さな単位に分けるか、元のmanifestとlockfileを復元して差分の起点からやり直します。ここでいう「止める」は復旧を断念する意味ではなく、意図しないversion変更を含むlockfileをcommitしないための安全策です。原因の候補、確認できた事実、未確認点、次に必要な担当者の操作を短く記録すれば、同じログを何度も読み直さずに作業を再開できます。
まとめ:最小手順
-
実行場所、Node/npm版、コマンド、エラー全文を記録する。
-
npm ci対象にpackage.jsonとnpm lockfileがあり、workspace rootが正しいか確認する。
-
manifestとlockfileの差分を読み、変更が意図的かを確認する。
-
lock生成時とCI実行時のnpm版、tree-shaping flags、project .npmrcを比較する。
-
原因に合う修正だけを行い、lockfileの広い差分をレビューする。
-
clean環境で同じ条件のnpm ciを実行し、CIでも再現性を確認する。
-
実施した検証と未確認条件を分けて記録する。
判断の目安は単純です。依存を変えたならmanifestとlockを対にしてレビューし、依存を変えていないなら、先に片方だけ変わった経緯を探します。tree-shaping flagやnpm版が違うなら、CIとlock生成側の条件を一致させてから再試行します。ログがERESOLVE、認証、通信、ファイル欠落を示すなら、このEUSAGE向け修復を流用しません。lockfile更新やnpm ciを実際に試せていないときは未検証と明記し、成功したように書かないことが最終判断の条件です。
参考資料
確認日:2026-10-15。npm CLI v11の公式文書を確認。説明は一般的な診断手順であり、特定repositoryのpackage.json、lockfile、CI環境を実行検証した結果ではありません。
npm ci: https://docs.npmjs.com/cli/v11/commands/npm-ci/(新しいタブで開く)
npm install: https://docs.npmjs.com/cli/v11/commands/npm-install/(新しいタブで開く)
package-lock.json: https://docs.npmjs.com/cli/v11/configuring-npm/package-lock-json/(新しいタブで開く)
npm config (legacy-peer-deps): https://docs.npmjs.com/cli/v11/using-npm/config/(新しいタブで開く)
