Claude Media
Claude CodeでChangesetsを回す — changesetは書かせてpublishは止める

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

    変更ごとにchangesetを足す

    changeset add(引数なしのchangesetでも同じ)で、.changeset/にファイルが1枚増えます。

  2. 2

    リリース時にversionを実行する

    changeset versionが、溜まったchangesetを消費してバージョンとchangelogを更新します。

  3. 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 0

settings.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=main

PRを出す前にこのコマンドを実行させ、終了コードが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. 1

    mainへのマージを止める

    リリース担当(RC)が、ベースブランチへのマージを一時的に止めます。

  2. 2

    versionを実行してPRにする

    ベースブランチを取得してchangeset versionを実行し、バージョン更新分を新しいPRにします。ここで生成されたchangelogとバージョンを確認します。

  3. 3

    マージ後にpublishする

    PRがマージされたらもう一度ベースブランチを取得し、changeset publishを実行します。

  4. 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を依頼して拒否されることを確かめれば、設定が効いているか判断できます。

この記事を共有:XはてブLinkedIn