changesets を AI エージェントに書かせると変更履歴が読める物になる
8 つのサイトを 1 つのモノレポで運用していて、変更のほとんどを AI エージェント(Claude Code)に PR として作らせています。エージェントの PR は 1 日に何本も merge されるので、しばらく経つと「あのサイトはいつ何が変わったのか」を私自身が追えなくなりました。git log はあります。ただ、commit のメッセージは「何をしたか」の一行で、「なぜ、何が壊れていて、何を捨てたか」はそこに無い。この記事は、changesets を入れて、エージェントに変更記録を書かせることで、変更履歴が人間の読める物になった記録です。別の bot のリポジトリでも同じ形にしていて、そちらで踏んだ罠も併記します。
changesets が何をする道具か
changesets は、パッケージのバージョンと CHANGELOG を管理する道具です。PR に .changeset/<名前>.md という小さなファイルを添え、そこに「どのパッケージが、どの種類の変更(patch / minor / major)で、何が変わったか」を書きます。main に merge されたこのファイルが溜まると、GitHub Actions(`changesets/action`)が「Version Packages」という PR を自動で立て、それを merge した時点で各パッケージの version が上がり、CHANGELOG.md に記録が集約されます。
.changeset/
config.json # changelog の生成に @changesets/changelog-github(PR と commit へのリンク付き)
restore-dropped-emdash-patch-hunks.md # PR に添える変更記録(merge 後、Release PR で消える)
.github/workflows/
release.yml # main への push で changesets/action を実行。Release PR を作る
apps/
<site>/CHANGELOG.md # サイトごとに集約された履歴
<site>/package.json # version が上がる。全部 private なので npm には出さないモノレポ側では、apps/* はすべて private: true で npm には公開しません。それでも使う理由は、バージョン番号ではなく「サイトごとの CHANGELOG に、人間が読める変更記録を自動で集める」ことです。2026-04 に入れてから、Release PR は 18 回 merge されています。
エージェントに何を書かせるか
変更記録の書き方を、エージェントの手順書(CLAUDE.md)に 3 行で決めています。
- 読み手(将来の自分、別のメンバー)が「何が、なぜ変わったか」を追えるレベルで書く
- commit のメッセージより詳しく、PR の本文より簡潔に
- 迷ったら書く。履歴を残して困ることはない
changeset を書かなくてよいのは、CI やインフラや文書の変更のように、サイトの挙動に影響しないものだけです。
ここが AI エージェントと相性の良いところです。人間がやると、変更記録は PR を出す直前に「あとで書く」になりがちで、書く頃には文脈が薄れています。エージェントは、PR を作るその瞬間に、Issue と差分と自分の調査の全部を持っている。そこで書かせた記録は、人間があとから書くより濃い。逆に言えば、エージェントの作業の文脈はセッションが終われば消えるので、その場で書かせないと二度と書けません。
実例: 前の記録の誤りを、次の記録が訂正する
2026-09-02 に、使わなくなったプラグインを撤去する PR を merge しました。その変更記録には、emdash 本体に当てているパッチについて「残るのは 404 ログ関連の 2 つだけ」と書いてあります。これが誤りでした。同じファイルに載っていた別目的の 2 つの修正(タクソノミーの先読み停止と、サイトマップの正規表現)も一緒に落ちていて、翌日、監視が D1 の読み取り行数の急増を検知しました。
戻す PR の変更記録には、こう書かせました。
#385 の変更記録にある「残るのは 404 ログ関連の 2 つだけ」はこの取りこぼしを見落としている。
CHANGELOG を開くと、0.9.3 の欄に、誤った記録と、それを訂正する記録が並んでいます。どちらも消していません。git log でこの関係を読み取るには、2 つの commit を開いてパッチの中身を比べる必要がありますが、CHANGELOG では 2 段落を続けて読むだけです。変更履歴が「読める物」になった、というのはこういうことです。
別のリポジトリで踏んだ罠
Slack から X に投稿する bot のリポジトリにも同じ形を入れました。こちらは単一パッケージで、private のまま git のタグと GitHub Release まで自動で作っています(privatePackages: { version: true, tag: true })。3 つ、モノレポ側では出なかった罠がありました。
- タグの系統が 2 つになった。 それまで「main への push ごとに patch を機械的に上げてタグを切る」ワークフローがあり、changesets のリリースと役割が丸ごと重なっていました。共存させると、通常の PR の merge のたびに patch タグが切られ、Version Packages PR の merge では Release が 2 つできます。古い方を消し、バージョン番号の意味付け(patch / minor / major)は changesets だけが持つことにしました
- changeset の忘れ検知は、commit 済みのファイルしか数えない。 CI に
changeset status --since=origin/mainを入れて、ソースの変更があるのに changeset が無い PR を検知しています。このコマンドは、作業ツリーにある未 commit の changeset を数えません。手元で「あるのに無いと言われる」を踏んで分かりました。運用の初期はcontinue-on-error: trueの警告扱いにし、changeset が要らない PR はskip-changesetのラベルで除外しています。定着したら警告を外すだけで必須にできます - Version Packages PR の merge も本番デプロイになる。 このリポジトリは main への push でデプロイが走ります。Version Packages PR の差分は
package.jsonの version とCHANGELOG.mdと changeset ファイルの削除だけで、コードの実体は変わらず、Terraform も no-op です。無害ですが、「Release PR の merge でデプロイが 1 回走る」ことは知っておく必要があります
境界: 3 つの場所に何を書くか
commit メッセージ、PR の本文、changeset の 3 つは役割が違います。
- commit: 何をしたか。1 行
- PR の本文: なぜやったか、どう確かめたか、何に詰まったか。レビューの材料。merge されれば PR の画面にしか残らない
- changeset: 使う側から見て何が変わったか。CHANGELOG に永久に残り、あとから読まれる前提で書く
changeset に PR の本文を写すと長すぎ、commit のメッセージを写すと短すぎます。「コミットより詳しく、PR 本文より簡潔に」はこの 3 つの間の線です。
どこで止まるか
- changeset の質は、書かせる指示の質で決まります。「変更を記録して」だけだと、commit メッセージの言い換えが出てきます。「何が、なぜ、何を捨てたか」と、読み手を指定して初めて読める物になります
- CHANGELOG はパッケージ単位に集約されます。モノレポで 8 つのサイトに同じ修正を当てたときは、8 つの CHANGELOG に同じ段落が入ります。1 か所で読みたい場合は Release PR の本文を見る方が早い
- 誤った記録は残ります。それが良いところでもあり、読み返す人は「訂正が続いていないか」まで読む必要があります
この構成で気をつけること
- 変更記録は、文脈が濃い瞬間に、その場で書かせる。エージェントの文脈はセッションが終わると消える
- 読み手を指定する。「将来の自分と別のメンバーが、何が、なぜ変わったかを追える」
- 迷ったら書く。書かない条件(挙動に影響しない変更)だけを決めておく
- リリースの自動化を 2 系統持たない。タグを切る主体は 1 つ
- 誤った記録は消さず、次の記録で訂正する
参考リンク
- changesets/changesets
- changesets/action(Version Packages PR を作る GitHub Action)
- @changesets/changelog-github(PR と commit へのリンク付きの CHANGELOG)