Claude CodeでChangesetsを回す — changesetは書かせてpublishは止める
Claude CodeにChangesetsのchangesetファイルを書かせ、changeset publishはdenyで止め、versionは承認に回す。CLAUDE.mdの規約とsettings.jsonの設定例をまとめます。
Changesetsの運用では、PRごとに.changeset/へ1枚のMarkdownを足し、リリース時にまとめて消費します。このうちClaude Codeに任せやすいのは、変更の都度changesetファイルを書く作業です。逆に、changeset publishはnpmへ公開する操作なので、権限ルールで止めて人の手に残します。
この記事では、その分担をCLAUDE.mdの規約とsettings.jsonのpermissionsで実現する手順を扱います。
3つのコマンドのうち、Claudeに渡すのは1つ
Changesetsの流れは、リポジトリ側のドキュメントで3段に整理されています。
Changesetsのリリースループ
- 1
変更ごとにchangesetを足す
changeset add(引数なしのchangesetでも同じ)で、.changeset/にファイルが1枚増えます。 - 2
リリース時にversionを実行する
changeset versionが、溜まったchangesetを消費してバージョンとchangelogを更新します。 - 3
確認してからpublishする
changeset publishが、npmに未公開のバージョンを公開します。
1段目は、変更を入れた本人が判断する内容です。どのパッケージが、semverのどの種別で上がり、利用者に何を伝えるか。コードを書いたClaudeがいちばん正確に書けます。
2段目はpackage.jsonとchangelogを書き換えます。Changesets側は、この段階でchangelogとバージョンの両方を人が確認することを勧めています。
3段目は、取り消しにくい操作です。publishは各パッケージのpackage.jsonのバージョンがnpmに無ければnpm publishを走らせ、gitタグも作ります。pnpmを使うリポジトリではpnpm publishが使われます。
つまり権限は次の割り当てになります。
| 段 | コマンド | 割り当て |
|---|---|---|
| 1 | コマンドchangeset(add)、changeset status | 割り当てallow |
| 2 | コマンドchangeset version | 割り当てask(毎回確認) |
| 3 | コマンドchangeset publish、npm publish、git push | 割り当てdeny |
changesetファイルの形を先に知る
changesetは、YAML front matter付きのMarkdownです。front matterにパッケージ名とバージョン種別、本文にchangelogへ載せる文章を書きます。
---
"@acme/ui": minor
---
Button に loading 属性を追加。
ボタン押下中の二重送信を防ぎたい場合は loading を true にする。ファイル名は.changeset/直下のUNIQUE_ID.md形式です。リポジトリ側のドキュメントには、生成後のファイルを自分で直してよいこと、最初から自分で書いてもよいことが明記されています。そのため、対話式のプロンプトを操作させなくても、ClaudeにMarkdownを直接書かせる運用が成り立ちます。
本文に何を書くかについて、同じドキュメントは3点を挙げています。
- WHAT: 何を変えたか
- WHY: なぜ変えたか
- HOW: 利用者はコードをどう直すか
1つのPRに複数のchangesetを足してもかまいません。変更ごとにchangelogの項目を分けたいとき、またパッケージごとに文面を変えたいときが該当します。
changesetが要らない変更
すべての変更にchangesetが要るわけではありません。テストやビルド設定だけの変更はリリースに関係しないので、ドキュメントも「changesetが無いPRを一律にブロックする運用は勧めない」としています。リリースを伴わないが記録は残したい場合は、空のchangesetを作ります。
npx changeset --empty空のchangesetは、front matterだけで本文が無いファイルになります。Claudeにはこの区別も規約として渡しておくと、テストだけの変更にまでminorのchangesetを作る事態を避けられます。
CLAUDE.mdに書く規約
CLAUDE.mdには、いつchangesetを足すか、どう書くか、どこから先は人の担当かを置きます。
## Changesets
- 公開パッケージの挙動・型・依存が変わるPRでは、`.changeset/` に changeset を1枚以上足す
- バージョン種別は semver に従う。破壊的変更は major、機能追加は minor、修正は patch
- 本文は WHAT / WHY / HOW の順で書く。HOW は利用者が直す点だけ
- テスト・CI・内部ツールだけの変更は `npx changeset --empty` を使う
- PR を出す前に `npx changeset status --since=main` を実行し、終了コードを確認する
- `changeset version` と `changeset publish` は実行しない。必要なら実行すべきコマンドを提示する最後の1行は、権限ルールと同じ向きのことを言葉でも伝えるためのものです。ただしCLAUDE.mdはClaudeの振る舞いを寄せるだけで、Claude Codeが何を許すかは変えません。止める仕組みは次のsettings.jsonが担います。
settings.jsonに分担を書く
Claude Codeの権限ルールは、deny、ask、allowの順に評価され、最初に当たったものが結果になります。広いdenyは、より狭いallowがあっても優先されます。そのため、publishを止めるdenyは他のallowに上書きされません。
{
"permissions": {
"allow": [
"Bash(npx changeset status *)",
"Bash(npx changeset add *)",
"Bash(npx changeset --empty)",
"Edit(/.changeset/*.md)"
],
"ask": [
"Bash(npx changeset version *)",
"Edit(/.changeset/config.json)"
],
"deny": [
"Bash(npx changeset publish *)",
"Bash(npx @changesets/cli publish *)",
"Bash(pnpm changeset publish *)",
"Bash(pnpm exec changeset publish *)",
"Bash(yarn changeset publish *)",
"Bash(npm publish *)",
"Bash(pnpm publish *)",
"Bash(git push *)"
]
}
}各行の意味は次のとおりです。
- 末尾の
*は、前に空白があれば引数なしのコマンドにも一致します。Bash(npx changeset publish *)はnpx changeset publishそのものも止めます Edit(/.changeset/*.md)の先頭の/は、プロジェクト設定ではプロジェクトルートからの相対パスを指しますchangeset versionをdenyでなくaskにしたのは、ファイルの書き換えにとどまるからです。承認画面で実行内容を確かめ、差分をgitで見てから進められます.changeset/config.jsonは、公開範囲(access)や対象外パッケージ(ignore)を決めます。設定ファイルの書き換えは承認を挟みます
git pushをdenyにしているのは、changeset publishの後にgit push --follow-tagsでタグを送る手順が、Changesets側で想定されているからです。公開とタグの送信は、人がまとめて実行する前提にしておくと、取りこぼしが起きにくくなります。作業ブランチへのpushはClaudeに任せたいチームでは、git push *の代わりにBash(git push --follow-tags *)だけをdenyにする手もあります。
同じ「実行は通し、公開は止める」設計は、Azure Functionsのfunc startとpublishやPackerのvalidateとbuildでも使えます。生成物に触らせない側のdenyはGraphQL Codegenの設定が参考になります。
denyが止めない書き方と、hookの網
Bashのdenyルールは、Claudeが普段書くコマンド文字列に一致する範囲しか止めません。次の書き方は、上のdeny一覧では止まらない可能性があります。
| 書き方 | 止まらない理由 |
|---|---|
bash -c 'npx changeset publish' | 止まらない理由外側がbashなので、ルールの文字列と一致しない |
npx -y changeset publish | 止まらない理由npxのフラグ入りで、ルールとの並びが変わる |
node_modules/.bin/changeset publish | 止まらない理由実行ファイルをパスで直接呼んでいる |
Claude Codeはtimeoutやniceなどの固定のラッパーだけを取り除いて照合し、npxはその一覧に入っていません。このため、npx経由の形はルール側に1つずつ書きます。
別の守り方として、PreToolUseフックでコマンド全文を検査できます。終了コード2を返すと呼び出しがブロックされ、標準エラーの文面がClaudeに返ります。.claude/hooks/block-publish.shの例です。
#!/usr/bin/env bash
cmd=$(jq -r '.tool_input.command // empty')
pattern='(changeset|@changesets/cli)[[:space:]]+(-[^[:space:]]+[[:space:]]+)*publish|(npm|pnpm|yarn)[[:space:]]+publish'
if echo "$cmd" | grep -Eq "$pattern"; then
echo "公開は人が実行します。changeset の追加までで止めてください" >&2
exit 2
fi
exit 0settings.jsonには、hooks.PreToolUseのmatcherをBashにして、このスクリプトをcommandとして登録します。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-publish.sh"
}
]
}
]
}
}フックの検査は文字列の部分一致です。意図的な迂回を完全には防げません。公開を確実に止めたいなら、Claudeの実行環境にnpmの公開用の認証情報を置かないことが、いちばん確実な線引きです。changeset publishは、npmで認証と書き込みが有効な場合にワンタイムパスワードを求めます。ワンタイムパスワードを手元に持たない環境では、ルールをすり抜けた場合でも、公開の最後の段階で人の入力が必要になります。
changesetの有無を確かめさせる
changeset statusは、パッケージに変更があるのにchangesetが無いとき、終了コード1で終わります。変更が無ければ失敗しません。
npx changeset status --since=mainPRを出す前にこのコマンドを実行させ、終了コードが1ならchangesetを足す、という反復をClaudeに回させられます。--sinceを省くと、.changeset/config.jsonのbaseBranchが基準になります。ドキュメントの設定例ではbaseBranchがmasterになっているため、既定のブランチがmainのリポジトリでは値を確かめてください。
注意点が2つあります。
statusは、versionやpublishの実行途中だと失敗します。バージョン更新の直前に実行します- CIでこの終了コードを使ってマージをブロックするかどうかは別の判断です。Changesets側は、changeset botでPRに促すだけの非ブロック運用を勧めています。ブロックしたいときは、
changeset status --since=mainをCIの手順に足します
人が進めるリリース手順
Claudeがchangesetを足したPRがmainに入った後は、人がversionから公開までを進めます。リポジトリ側が勧める手動の流れは次のとおりです。
リリース担当が行う手順
- 1
mainへのマージを止める
リリース担当(RC)が、ベースブランチへのマージを一時的に止めます。
- 2
versionを実行してPRにする
ベースブランチを取得して
changeset versionを実行し、バージョン更新分を新しいPRにします。ここで生成されたchangelogとバージョンを確認します。 - 3
マージ後にpublishする
PRがマージされたらもう一度ベースブランチを取得し、
changeset publishを実行します。 - 4
タグを送って再開する
git push --follow-tagsでタグを送り、マージを再開します。
手数が多いと感じる場合は、Changesetsが提供するGitHub Actionを使う方法があります。ActionがバージョンPRを作って更新し続け、そのPRのマージを公開の合図にできます。この構成なら、Claudeの端末でpublishを止めるだけでなく、公開そのものをCIに寄せられます。人の承認は「バージョンPRをマージする」操作になります。
なおリポジトリ内のChangesetsのドキュメントには、内容が古く、最新版はchangesets.devに移ったという注記が付いています。コマンドの細部は、手元の@changesets/cliのバージョンで--helpを見て確かめるのが安全です。
つまずきやすい点
- モノレポでパッケージ名がずれる: front matterのキーは
package.jsonのnameと完全に一致させます。Claudeに推測させず、ls packagesの結果を読ませてから書かせます - version後にコミットを挟む:
publishは直前のコミットがリリースコミットである前提です。versionとpublishの間に別の変更をコミットしません - 非公開パッケージの扱い:
privatePackagesの設定で、バージョン更新とタグの付与を個別に選べます - pnpmを使うリポジトリ:
publishは自動でpnpm publishに切り替わるため、npm publishだけをdenyに入れても足りません。上の一覧のように、使っているパッケージマネージャーの形を並べます
まとめ
changesetの追加はClaudeに、versionは承認付きで、publishとgit pushは人に、という3段の割り当ては、CLAUDE.mdとpermissionsの組み合わせで書けます。denyは通常の書き方を止める仕組みなので、npx経由の形はルールに並べ、確実に止めたい場合はフックと、公開トークンを渡さない環境で補います。
試す順番は、まずallowとdenyだけを入れて、Claudeにchangesetを1枚書かせることです。changeset publishを依頼して拒否されることを確かめれば、設定が効いているか判断できます。