Claude Media
Claude Codeでpackage-lockのコンフリクトを直す — 手編集せず再生成させる手順

Claude Codeでpackage-lockのコンフリクトを直す — 手編集せず再生成させる手順

package-lock.jsonとpnpm-lock.yamlの衝突マーカーを手で消さず、package.jsonを先に直してinstallで再生成させる手順です。Editのdeny、CLAUDE.mdの規約、検証スクリプトまで示します。

マージやリベースでpackage-lock.jsonやpnpm-lock.yamlが衝突したとき、Claude Codeに衝突マーカーを1つずつ消させるのは避けたい作業です。lockfileはツールが生成するファイルで、手で整合させる前提で作られていません。直す場所はpackage.jsonだけにして、lockfileはinstallコマンドで作り直し、できた差分を検証する。この順番にすると、lockfileの整合をinstallに任せられ、手で縫い合わせたときのような食い違いが入り込みにくくなります。

この記事では、npmとpnpmそれぞれの再生成手順、Claude Codeにlockfileを手編集させないためのEditのdenyとCLAUDE.mdの書き方、直したあとの検証スクリプトを順に組みます。生成物を直させず再生成させる考え方は、OpenAPI Generatorの設定やsqlcのhook設定と同じ型です。

lockfileの衝突マーカーを手で消すと何が壊れるか

package-lock.jsonは、npmがnode_modulesかpackage.jsonを変更する操作のたびに自動生成されるファイルです。実際に作ったツリーを正確に記録し、次回以降のinstallが同じツリーを再現できるようにするためにあり、リポジトリにコミットする前提で設計されています。つまり内容は人が書く設計図ではなく、ツールが出力した記録です。

記録を手で縫い合わせると、次のような壊れ方をします。

  • JSONやYAMLとして構文は通っても、package.jsonの依存宣言と食い違う
  • 片方のブランチで解決したバージョンと、もう片方で追加した依存の解決結果が混ざり、どのinstallでも出ない組み合わせになる
  • 依存の整合性情報が1行ずれても、CIで初めて落ちる

とくに3つめは気づきにくい点です。衝突マーカーを消した直後のローカルでは動いていても、npm ciのようにlockfileを凍結して入れる環境で失敗が表に出ます。npm ciはpackage.jsonとlockfileの依存が一致しないとエラーで終了し、lockfileを更新しません。pnpmもCI環境では、lockfileの更新が必要な状態だとinstallが失敗します。

lockfile

手で直さないほうがよい理由

  • ツールの出力だから

    package-lock.jsonはnpm installが書き出す記録で、人が整合性を保つ書式ではありません。

  • 衝突の中身が機械的だから

    両方のブランチが依存を足しただけの衝突なら、package.jsonを直して再解決すれば消えます。

  • 検証手段がinstallにあるから

    npm ciやpnpmの凍結installが、手編集の食い違いを確実に拾います。

直す順番はpackage.jsonが先、lockfileは最後

衝突を見つけたら、Claude Codeには次の順で作業させます。

手順

lockfile衝突の解消手順

  1. 1

    衝突ファイルを一覧する

    git statusで衝突しているファイルを確認します。package.jsonとlockfileの両方が衝突していることが多く、lockfileだけの衝突もあります。

  2. 2

    package.jsonの衝突だけを手で解消する

    両ブランチで足された依存は両方残し、同じ依存のバージョン範囲が食い違うときはどちらを採るかをここで決めます。判断が要るのはこのファイルだけです。

  3. 3

    lockfileはそのままinstallで再生成する

    npmはnpm install --package-lock-only、pnpmはpnpm installを、衝突したlockfileを残したまま実行します。

  4. 4

    差分を検証して、lockfileをコミットする

    凍結installと差分確認を通ってからgit addします。

package.jsonの解消は、人が意図を決めるべき部分です。たとえば両側が同じ依存を別のバージョン範囲に上げていたなら、どちらの範囲に揃えるかはプロジェクトの方針で決まります。Claude Codeには、両側の差分を見せたうえで「範囲が食い違う依存は一覧にして聞く」と指示しておくと、勝手に片方へ寄せられずに済みます。

npmの手順 — package.jsonを直してnpm installにmergeさせる

npmのlockfileの衝突は、package.jsonの衝突を手で直したあとにnpm installを実行すると解決できます。npm@5.7.0以降の挙動として、npm v6のドキュメント(package-locks)に書かれていた手順です。npmが衝突を解決し、両ブランチの依存を含むlockfileを書き出します。--package-lock-onlyを付けると、node_modulesには触れずlockfileだけを更新します。現行のnpm v11のドキュメントでは、npm installのページにもpackage-lock.jsonのページにも、この衝突解決の記述はありません。手順の出典が旧版である点は、念頭に置いてください。

# package.json の衝突を解消したあと、lockfile は触らず実行する
npm install --package-lock-only
git add package.json package-lock.json

この手順でlockfileが壊れたり、想定外の依存が動いたりしたときの代替が、片方のlockfileをいったん丸ごと採用し、package.jsonとの差をnpm installに埋めさせる方法です。

git merge mainで衝突した場合、ブランチの取り込み先であるmain側のlockfileを残しておくと、再解決されるのは自分のブランチで足した依存だけになります。git checkoutの--oursと--theirsは、mergeでは--oursが現在のブランチ、--theirsが取り込む側を指します。

git checkout --theirs package-lock.json   # main 側の lockfile を採用
npm install                               # package.json との差を埋める
git add package.json package-lock.json

リベースのときは向きが逆になります。git rebaseとgit pull --rebaseの最中はoursとtheirsが入れ替わって見え、--oursが土台になるブランチ、--theirsが自分の作業側のコミットを指すためです。リベース中に同じ操作をするなら、main側を採用したい場面で--oursを使います。向きを取り違えると、自分のブランチ側のlockfileを残してしまい、mainで更新された依存を巻き戻します。Claude Codeに任せるときは、mergeかrebaseかを指示に書いておきます。

落とし穴

向きを間違えると main の更新が巻き戻る

リベース中に--theirsを選ぶと、mainではなく自分のコミット側のlockfileが残ります。そのままinstallしても整合は取れるので、エラーにならずに他人の依存更新だけが消えます。採用したのがどちら側か、git diff main -- package-lock.jsonの行数で確かめます。

npm installで埋めた結果、他方で足された依存は新しく解決されるため、main側で固定されていたバージョンと一致しないことがあります。想定外に上がった依存がないかは、検証の段で差分を読んで確かめます。

pnpmの手順 — pnpm installにマージを任せる

pnpmは、pnpm-lock.yamlの衝突を自動で解消できます。衝突したらpnpm installを実行してコミットする手順です。両側がどちらも有効なlockfileであれば、pnpmがそれをマージします。それ以外の場合は構文エラーで失敗するので、結果を確認してからコミットします。

# package.json の衝突を解消したあと、lockfile は触らず実行する
pnpm install
git diff --stat pnpm-lock.yaml
git add package.json pnpm-lock.yaml

自動マージが使えるのは、衝突した両側が有効なlockfileのときだけです。Claude Codeが先に衝突マーカーを部分的に消していると、その前提が崩れてパースエラーになりかねません。手編集を許さない設定が効くのはこの点です。パースエラーが出たときは、手で直そうとせず、git checkout --theirs pnpm-lock.yamlで片方を採用してからpnpm installし直します。

Claude Codeにlockfileを手編集させない設定

作業手順だけをプロンプトで毎回伝えるのは確実ではありません。設定で手編集の入口を塞いでおくと、衝突の場面でも迷いません。

Editのdenyルールは、Claude Codeの組み込みのファイル編集ツールに加えて、sedやtee、リダイレクトなど、Claude Codeが認識するBashのファイル操作にも効きます。パスはgitignore形式で書き、ファイル名だけを書けば階層を問わず一致します。

{
  "permissions": {
    "deny": [
      "Edit(package-lock.json)",
      "Edit(pnpm-lock.yaml)"
    ]
  }
}

この設定を.claude/settings.jsonに置いても、npm installやpnpm installによるlockfileの再生成は妨げられません。denyルールは任意のサブプロセスがファイルを書く動作までは見ないためです。言い換えると、手編集は止まり、再生成は通ります。npmやpnpmの実行自体を許可するかどうかは、別にBash(npm install)やBash(pnpm install)のallowルールで決めます。

規約はCLAUDE.mdにも置きます。

## lockfileの扱い
- package-lock.json と pnpm-lock.yaml は直接編集しない。衝突マーカーも手で消さない。
- 衝突したら先に package.json の衝突だけを解消する。バージョン範囲が食い違う依存は、
  一覧にして私に確認する。
- そのあと npm なら `npm install --package-lock-only`、pnpm なら `pnpm install` を実行する。
  失敗したら `git checkout --theirs package-lock.json`(rebase 中は --ours)で
  main 側を採用してから `npm install` し直す。
- git add の前に、検証スクリプト scripts/check-lock.sh を実行して通す。

deny設定とCLAUDE.mdの役割は分かれています。denyは破ってほしくない一線を機械的に止め、CLAUDE.mdは「その代わりに何をするか」を伝えます。denyだけだと、Claude Codeは編集を拒否されたあとの代替手段を探して迷います。

直したあとに検証する

再生成したlockfileは、衝突マーカーが残っていないことと、package.jsonと食い違っていないことの2点を確かめてからコミットします。次のスクリプトをscripts/check-lock.shに置き、Claude Codeに実行させます。

#!/bin/bash
set -e
 
# 1. 衝突マーカーが残っていないこと
if grep -nE '^(<<<<<<<|=======|>>>>>>>)' package.json package-lock.json pnpm-lock.yaml 2>/dev/null; then
  echo "衝突マーカーが残っています" >&2
  exit 1
fi
 
# 2. lockfile が package.json と一致すること(ファイルは書き換えない)
if [ -f pnpm-lock.yaml ]; then
  pnpm install --frozen-lockfile
elif [ -f package-lock.json ]; then
  npm ci
fi
 
# 3. 差分の規模を見る
git diff --stat -- package-lock.json pnpm-lock.yaml

2番目のnpm ciは、依存が一致しなければエラーで終わり、package.jsonにもpackage-lock.jsonにも書き込みません。pnpm install --frozen-lockfileも、lockfileがpackage.jsonと食い違っていれば失敗する設定です。どちらもlockfileとpackage.jsonは書き換えないので、繰り返し実行してもlockfileは変わりません。node_modulesは入れ直されます。npm ciはnode_modulesが既にあると先に削除してから入れ直すため、時間はかかります。

最後の差分は、行数ではなく中身を読みます。次の3点をClaude Codeに要約させると、見落としが減ります。

  1. package.jsonに書いていない依存のバージョンが動いていないか
  2. 自分のブランチで足した依存以外に、lockfile上で大きく動いたものはないか
  3. main側で更新された依存が巻き戻っていないか

3点目は、先ほどのリベース中の取り違えを拾う項目です。git diff main -- package-lock.jsonで、mainとの差が自分の変更に対応する範囲かを見ます。

lockfileを再生成するときは、--legacy-peer-depsのようなinstallのフラグを、CIのnpm ciと同じ組み合わせにそろえます。フラグが違うと、手元で再生成したlockfileがCIの検証と食い違うことがあります。

検証をClaude Codeの作業の終わりに必ず走らせたいなら、hookで自動化する手もあります。テストを自動実行する方式はhooksでテストを自動実行する記事にまとめています。

衝突そのものを減らす — pnpmのブランチ別lockfile

pnpmには、lockfileの衝突自体を避ける設定があります。pnpm-workspace.yamlにgitBranchLockfile: trueを書くと、lockfile名が現在のブランチ名から作られます。たとえばブランチ名がfeature-1なら、生成されるのはpnpm-lock.feature-1.yamlです。ブランチ名のうちASCII英数字と_、.、-以外の文字は!に置き換わり、小文字化されます。

# pnpm-workspace.yaml
gitBranchLockfile: true
mergeGitBranchLockfilesBranchPattern:
  - main
  - release*

ブランチ別のlockfileは、pnpm install --merge-git-branch-lockfilesで1つのpnpm-lock.yamlにまとめられ、まとめたあとのブランチ別ファイルは削除されます。mergeGitBranchLockfilesBranchPatternを設定しておくと、mainやreleaseで始まるブランチでpnpm installを実行したときに、自動でマージされます。

ただしこの設定は、衝突の解消を先送りにする仕組みです。ブランチ別のlockfileが増えると、コミットされるファイルも増えます。衝突が週に何度も起きるリポジトリでは効果が大きく、まれにしか起きないなら、ここまでの手順で十分です。この設定はpnpmのものなので、npmのプロジェクトでは、ブランチを短命にしてmainを頻繁に取り込む運用が現実的です。

まとめ

lockfileの衝突は、衝突マーカーを消す作業ではなく、package.jsonの意図を決めてからinstallをやり直す作業です。人が判断するのはpackage.jsonの食い違いだけで、lockfileの中身は再生成に任せます。Editのdenyで手編集の入口を止めておき、CLAUDE.mdに代わりの手順を書いておけば、衝突の場面でClaude Codeが迷う余地は小さくなります。

最後に、直したあとの検証を省かないことが肝心です。npm ciやpnpm install --frozen-lockfileが通れば、少なくともpackage.jsonとの食い違いはありません。残る確認は、依存が想定外に動いていないかを差分で読むことです。依存の脆弱性を仕分ける作業はnpm auditとpip-auditの結果の仕分けに続けられます。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. docs.npmjs.com/cli/configuring-npm/package-lock-json
  2. docs.npmjs.com/cli/commands/npm-ci
  3. github.com/npm/cli/blob/v6.14.18/docs/content/configuring-npm/package…
  4. pnpm.io/git
  5. pnpm.io/git_branch_lockfiles
  6. pnpm.io/cli/install
  7. git-scm.com/docs/git-checkout
  8. code.claude.com/docs/en/permissions
  9. code.claude.com/docs/en/common-workflows
この記事を共有:XはてブLinkedIn