Claude CodeでAngularをng updateで上げる — 1メジャーずつng buildを通す
ng updateは一度に1メジャーしか進めません。段ごとにng buildを通す完了条件をStop hookで置き、migrationsの自動差分と手で直す差分をコミットで分けるClaude Codeの運用をまとめます。
Angularのメジャーバージョンを3つ遅れで放置していたアプリは、ng updateを3回回すことになります。Angular CLIは複数のメジャーをまたぐ更新を受け付けないからです。Claude Codeに任せるときは、この「1段ずつ」の制約に作業の区切りを合わせると、失敗した場所が特定しやすくなります。
この記事では、更新を1メジャーごとのコミットに分ける手順、各段でng buildが通るまで終わらせないStop hook、自動マイグレーションの差分と手で直した差分を履歴で分ける方法を扱います。
ng updateは一度に1メジャーしか進まない
Angularのバージョン方針には、ng updateが使える条件が書かれています。更新先のバージョンがサポート対象であること、そして更新元が更新先から見て1メジャー以内であることです。複数のメジャーをまたぐときは、1メジャーずつ順に更新します。バージョン10から12へ上げる例では、10から11、11から12の2回に分けます。
これは方針の文書だけの話ではありません。angular-cliの更新コマンドの実装には、更新先と現在のメジャーの差が1より大きいと失敗させる処理があります。エラー文言は次の形です。@angular/coreが19のまま22を指定したときに出る文言で、角括弧の中身は更新対象のパッケージ名に変わります。
Updating multiple major versions of '@angular/core' at once is not supported.
Please migrate each major version individually.
Run 'ng update @angular/core@20' in your workspace directory to update to
latest '20.x' version of '@angular/core'.Claude Codeに「Angularを最新にして」とだけ頼むと、package.jsonの数字を書き換えてnpm installで済ませようとする経路もありえます。ただしマイグレーションを実行するのはng updateです。数字を書き換えるだけでは、コードの自動修正が走らないまま依存関係だけが進みます。更新はng updateで行い、1段ずつ区切る、という前提を最初に渡しておくのが近道です。
1メジャー分の更新の流れ
- 1
作業ツリーを空にする
未コミットの変更があると
ng updateは止まります。前の段の変更はコミットしてから次へ進みます。 - 2
ng updateを実行する
@angular/cliと@angular/coreを次のメジャーに指定します。--create-commitsで更新とマイグレーションを別コミットにします。 - 3
ng buildとテストを通す
マイグレーションで直らなかった箇所を手で直します。手直しは自動の差分と別のコミットにします。
- 4
次のメジャーへ進む
ビルドとテストが通ったことを確かめてから、同じ手順を繰り返します。
段ごとに変わるNode.jsとTypeScriptの要求
更新が止まる理由はコードだけではありません。Angularの各バージョンは、動作するNode.js、TypeScript、RxJSの範囲を持っています。バージョン互換の表から、19系から22系までの要求を抜き出すと次のとおりです。
| Angular | Node.js | TypeScript |
|---|---|---|
| 19.2.x | Node.js^18.19.1 || ^20.11.1 || ^22.0.0 | TypeScript>=5.5.0 <5.9.0 |
| 20.2.x / 20.3.x | Node.js^20.19.0 || ^22.12.0 || ^24.0.0 | TypeScript>=5.8.0 <6.0.0 |
| 21.0.x 〜 21.2.x | Node.js^20.19.0 || ^22.12.0 || ^24.0.0 | TypeScript>=5.9.0 <6.0.0 |
| 22.0.x | Node.js^22.22.3 || ^24.15.0 || ^26.0.0 | TypeScript>=6.0.0 <6.1.0 |
たとえばNode.js 22.12系で作業しているなら、19から21までは同じNodeのまま進められます。22に入る段だけ、Nodeを22.22.3以上へ上げる必要があります。Node 18系で動いているアプリは、20への更新より先にNodeを上げなければなりません。
TypeScriptの許容範囲も段ごとに動き、22系は6.0系だけです。Claude Codeには、各段の最初に「この段のNode.jsとTypeScriptの要求は表のとおり」と伝え、満たしていなければng updateの前に環境を直させます。Nodeの切り替え方(nvm、Volta、miseなど)はプロジェクトによって違うので、CLAUDE.mdに1行書いておけば、毎回聞かれずに済みます。
CLAUDE.mdに1段ずつの規約を書く
更新作業の約束事は、セッションをまたいでも守られるようCLAUDE.mdに置きます。次の形が一例です。
## Angularのバージョン更新
- 更新は `ng update` で行う。package.jsonの数字だけを書き換えない
- 1回に進めるのは1メジャーだけ。`@angular/cli` と `@angular/core` を同じメジャーで指定する
- 実行前に `git status` で作業ツリーが空であることを確かめる
- 更新は `--create-commits` を付ける。`--force` と `--allow-dirty` は私が指示したときだけ使う
- 各段の終わりに `npx ng build` と `npm test` を通す。通らない間は次のメジャーへ進まない
- 手で直した差分は `fix:` で別コミットにする(マイグレーションのコミットに混ぜない)
- 環境(Node.jsのバージョンなど)が原因で止まったら、コードを直さず私に報告する最後の1行は、ng buildが環境の問題で落ちたときに、Claudeがソースを書き換えて回避してしまうのを防ぐためのものです。Node.jsのバージョン不一致を、無関係なコードの修正で埋めようとする動きは避けたいところです。
ng buildを通すまで終わらせないStop hook
CLAUDE.mdは「お願い」であり、守られるかどうかはモデル次第です。「ビルドが通るまでは終わらせない」を仕組みで担保するには、Stop hookが使えます。Stop hookはClaudeが応答を終えるたびに走り、decision: "block"を返すと、Claudeに続きの作業をさせられます。
hookの設定は、プロジェクトの.claude/settings.jsonに書きます。Stopイベントはmatcherに対応せず、毎回発火します。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/ng-build-gate.sh",
"timeout": 600
}
]
}
]
}
}スクリプト本体は次のようになります。ng buildが失敗したら、出力の末尾をClaudeに返して作業を続けさせます。
#!/bin/bash
INPUT=$(cat)
# すでにStop hookの結果で続行中なら、同じ条件で再びブロックしない
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0
fi
cd "$CLAUDE_PROJECT_DIR" || exit 0
[ -f angular.json ] || exit 0
OUT=$(npx ng build 2>&1)
if [ $? -ne 0 ]; then
jq -n --arg out "$(echo "$OUT" | tail -n 40)" \
'{decision: "block", reason: ("ng buildが失敗しています。末尾40行:\n" + $out)}'
fi
exit 0ポイントは3つあります。
- JSONは
jqで組み立てる: ビルドログには引用符や改行が混ざるため、文字列連結で作るとJSONが壊れ、hookのエラーになります。jqの--argなら自動でエスケープされます stop_hook_activeを確かめる: Stop hookはClaudeが止まろうとするたびに走ります。すでにhookの指示で続行中の場合にまたブロックすると、直らない条件でループします。Claude Codeは連続8回のブロックで上書きして止めますが、その前に自前で抜けるのが定石です- タイムアウトを延ばす: コマンド型のhookの既定は10分です。大きなアプリの
ng buildが10分に近いなら、timeoutを秒で明示します
この構成だと、hookが1回ブロックしたあとの再停止ではhookが素通りします。そのぶんCLAUDE.mdの「通らない間は次へ進まない」が効くので、hookは「ビルドを確かめずに終わりました」を拾う安全網、規約は「通るまで直す」を促す主役、という役割分担です。
アップグレード作業の間だけ有効にしたいときは、作業ブランチの上でだけhookを設定に足し、更新が終わったら外します。普段の開発で、Claudeが応答するたびにng buildが走る状態は重すぎます。
ng testをhookに入れる手もありますが、テストランナーはAngularのバージョンやプロジェクトの設定で変わります。npm testとしてプロジェクトに用意されているコマンドをCLAUDE.mdに書き、Claudeに実行させるほうが、バージョンをまたいでも壊れにくくなります。ビルドを完了条件にする考え方は、Mavenビルドでverifyを完了条件にする記事やsemantic-releaseのコミット検査でも同じ型を使っています。
自動の差分と手で直す差分を履歴で分ける
ng updateには、更新とマイグレーションのたびにコミットを作る--create-commits(短縮形は-C)があります。既定はオフです。これを付けると、パッケージの更新と各マイグレーションの結果が、それぞれ独立したコミットとして残ります。
git status --short # 空であることを確かめる
npx ng update @angular/cli@^20 @angular/core@^20 --create-commits
git log --oneline -n 10 # 更新とマイグレーションのコミットを見る
npx ng build # 通らなければここから手で直すマイグレーションが直せなかった箇所はng buildのエラーとして表に出ます。そこから先はClaudeが手で直し、fix:のコミットとして積みます。履歴はこう並びます。
ng updateが作るパッケージ更新のコミット- 各マイグレーションのコミット(自動修正の差分)
- Claudeの手直しのコミット(人が重点的にレビューする差分)
この分け方の利点は、レビューの重さを配分できる点です。自動のコミットはAngularチームが用意した変換なので、差分の傾向を流し見すれば足ります。手直しのコミットは、Claudeが判断して書いたコードなので、そこに時間をかけます。段が進んで履歴が長くなっても、どの差分が機械で、どの差分がClaudeかが残ります。
手直しの途中で方向を誤ったときも、マイグレーションまでのコミットは残っているので、手直し側だけをgit resetで巻き戻せます。更新そのものをやり直さずに済みます。
CLAUDE.mdの「手で直した差分はfix:で別コミット」を守らせるには、ng buildが通ったあとにClaudeが差分をまとめてコミットする流れにします。マイグレーション直後にビルドが落ちていたら、その時点の差分はすべて手直しなので、混ざる余地はありません。
途中で止まったときの切り分け
ng updateが止まる原因は、だいたい次の順で絞れます。
ng updateが進まないときの確認順
- Q作業ツリーは空か?答えがいいえのとき答えがはい
ng updateは未コミットの変更を嫌います。コミットまたはstashしてから再実行します。 - QNode.jsとTypeScriptは、更新先の要求範囲にあるか?答えがいいえのとき答えがはい
前の節の表と照らして、環境を先に直します。ソースの修正では解消しません。
- Qpeer dependencyの不一致で止まっていないか?答えがいいえのとき答えがはい
サードパーティのライブラリが更新先のAngularに対応していないのが原因です。
--forceで押し切る前に、そのライブラリの対応バージョンを確かめます。
ng buildまで進んだ
ここから先は、型エラーや廃止されたAPIの手直しです。Claudeにエラーを読ませて直させます。
--forceはpeer dependencyの不一致を無視するオプションです。一見便利ですが、非対応のライブラリを抱えたまま更新が進み、あとでビルドや実行時に別の形で壊れます。CLAUDE.mdで「私が指示したときだけ」と縛っておく理由はここにあります。
落とし穴
マイグレーションだけを再実行したいのに、更新ごとやり直してしまう
--migrate-onlyを付けると、インストール済みのバージョンは動かさず、マイグレーションだけを実行できます。更新対象が1つのパッケージのときに限り、--fromで適用の起点、--toで終点のバージョン、--nameで特定のマイグレーションを指定できます。マイグレーションが途中で失敗したとき、パッケージの更新をやり直さずに再試行する用途です。Claudeがこのオプションを知らずにng updateを丸ごと繰り返さないよう、止まり方を見てから指示します。
段数が多いときの進め方
3段以上ある更新は、1セッションで全部やらせると文脈が長くなり、後半の段で前半の判断を参照しにくくなる場合があります。段の区切りで会話を整理する運用が向いています。
- 1メジャーを終えるたびに、
ng buildとnpm testが通った状態でコミットし、/clearで会話を空にする - 次の段では、CLAUDE.mdの規約と
git logだけを手がかりに、現在のメジャーから「次の1つ」を判断させる - 途中で人が一息つけるよう、各段をブランチかプルリクエスト単位にしておく
履歴が1メジャーごとのコミットでまとまっていれば、/clear後のClaudeはgit log --onelineを読むだけで「どこまで終わったか」を取り戻せます。Angularの更新は、各段の終わりが明確なので、コンテキストを切り離しやすい作業です。
まとめ
Angularの更新で、Claude Codeに渡すべき情報は3つに絞れます。1メジャーずつ進めること、各段の完了条件がng buildとnpm testの通過であること、自動の差分と手直しをコミットで分けることです。--create-commitsと段ごとのコミットだけで、レビューと巻き戻しの単位が揃います。
hookは「ビルドを確かめずに終わる」穴を塞ぐ安全網です。止まった原因が環境にあるときは、コードを直させず人に戻す、という線引きも一緒に置いておくと、更新がきれいな形で積み上がります。AngularのMCPサーバーをClaude Codeに登録する手順はAngular CLIのMCP登録の記事にあります。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。