Claude Media
Claude CodeでAngularをng updateで上げる — 1メジャーずつng buildを通す

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. 1

    作業ツリーを空にする

    未コミットの変更があるとng updateは止まります。前の段の変更はコミットしてから次へ進みます。

  2. 2

    ng updateを実行する

    @angular/cliと@angular/coreを次のメジャーに指定します。--create-commitsで更新とマイグレーションを別コミットにします。

  3. 3

    ng buildとテストを通す

    マイグレーションで直らなかった箇所を手で直します。手直しは自動の差分と別のコミットにします。

  4. 4

    次のメジャーへ進む

    ビルドとテストが通ったことを確かめてから、同じ手順を繰り返します。

段ごとに変わるNode.jsとTypeScriptの要求

更新が止まる理由はコードだけではありません。Angularの各バージョンは、動作するNode.js、TypeScript、RxJSの範囲を持っています。バージョン互換の表から、19系から22系までの要求を抜き出すと次のとおりです。

AngularNode.jsTypeScript
19.2.xNode.js^18.19.1 || ^20.11.1 || ^22.0.0TypeScript>=5.5.0 <5.9.0
20.2.x / 20.3.xNode.js^20.19.0 || ^22.12.0 || ^24.0.0TypeScript>=5.8.0 <6.0.0
21.0.x 〜 21.2.xNode.js^20.19.0 || ^22.12.0 || ^24.0.0TypeScript>=5.9.0 <6.0.0
22.0.xNode.js^22.22.3 || ^24.15.0 || ^26.0.0TypeScript>=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:のコミットとして積みます。履歴はこう並びます。

  1. ng updateが作るパッケージ更新のコミット
  2. 各マイグレーションのコミット(自動修正の差分)
  3. Claudeの手直しのコミット(人が重点的にレビューする差分)

この分け方の利点は、レビューの重さを配分できる点です。自動のコミットはAngularチームが用意した変換なので、差分の傾向を流し見すれば足ります。手直しのコミットは、Claudeが判断して書いたコードなので、そこに時間をかけます。段が進んで履歴が長くなっても、どの差分が機械で、どの差分がClaudeかが残ります。

手直しの途中で方向を誤ったときも、マイグレーションまでのコミットは残っているので、手直し側だけをgit resetで巻き戻せます。更新そのものをやり直さずに済みます。

CLAUDE.mdの「手で直した差分はfix:で別コミット」を守らせるには、ng buildが通ったあとにClaudeが差分をまとめてコミットする流れにします。マイグレーション直後にビルドが落ちていたら、その時点の差分はすべて手直しなので、混ざる余地はありません。

途中で止まったときの切り分け

ng updateが止まる原因は、だいたい次の順で絞れます。

切り分け

ng updateが進まないときの確認順

  1. Q作業ツリーは空か?
    答えがいいえのとき

    ng updateは未コミットの変更を嫌います。コミットまたはstashしてから再実行します。

    答えがはい
  2. QNode.jsとTypeScriptは、更新先の要求範囲にあるか?
    答えがいいえのとき

    前の節の表と照らして、環境を先に直します。ソースの修正では解消しません。

    答えがはい
  3. 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登録の記事にあります。

参照した一次ソース

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

  1. angular.dev/cli/update
  2. angular.dev/reference/releases
  3. angular.dev/reference/versions
  4. github.com/angular/angular-cli/blob/main/packages/angular/cli/src/com…
  5. code.claude.com/docs/en/hooks-guide
  6. code.claude.com/docs/en/hooks
この記事を共有:XはてブLinkedIn