GitHub Immutable Releasesは、公開したリリースのアセット(配布ファイル)とGitタグを以後変更できなくする機能です。2025年8月26日にパブリックプレビューとして公開され、2025年10月28日に一般提供(GA)になりました。リポジトリの設定画面で「Enable release immutability」にチェックを入れるだけで有効になりますが、公開後にアセットを追加するリリースワークフローは422エラーで止まります。本記事では、固定される範囲、有効化の手順、draft経由の公開フロー、gh release verifyによる検証を、GitHubの公式ドキュメントと実行結果にもとづいて整理します。
まとめ:Immutable Releasesを有効化する前に押さえる5点
- 固定されるのはアセットとタグだけです。タイトル、リリースノート、プレリリース/Latestの指定は公開後も変更できます。
- 有効化はリポジトリの Settings → Releases、またはOrganizationの Settings → Repository → General で行います。対象は有効化以降に公開するリリースだけで、無効化しても不変になったリリースは元に戻りません。
- 公開済みリリースへのアセット追加は
Cannot upload assets to an immutable releaseで失敗します。draftで作成→アセット添付→公開の順に変えます。 - GitHub.comの不変リリースには署名付きのリリース証明(Sigstore bundle形式)が自動で付き、
gh release verifyとgh release verify-assetで検証できます。gh CLIは2.93.0以降を使います。 - リリースを削除すればタグも削除できますが、同じタグ名は再利用できません。同名リポジトリを作り直しても同じです。
Immutable Releasesで固定される範囲と変更できる項目
公式ドキュメント「Immutable releases」は、保護対象をタグとアセットの2つに限っています。タグは公開時点のコミットに固定され、リリースが存在する間は移動も削除もできません。添付したバイナリやアーカイブは変更も削除もできません。一方で、次の項目は公開後も編集できます。
| 操作 | 不変リリースでの可否 |
|---|---|
| アセットの追加・差し替え・削除 | 不可 |
| タグの移動(別コミットへの付け替え) | 不可 |
| リリースが存在する間のタグ削除 | 不可 |
| タイトル・リリースノートの編集 | 可 |
| プレリリース/Latestの切り替え | 可 |
| リリースの削除とその後のタグ削除 | 可 |
| 削除したタグと同名のタグの再作成 | 不可 |
同名タグを再利用できない制約は、リポジトリを削除して同じ名前で作り直した場合にも適用されます。公式ドキュメントはこれを「repository resurrection attacks」への対策と説明しています。攻撃者が削除済みリポジトリの名前を取り直し、既知のタグ名で別のバイナリを配布する手口を防ぐためです。
不変かどうかはリリースページのタイトル下に表示される「Immutable」ラベルで判別できます。APIではリリースオブジェクトのimmutableフィールド(boolean)で判別でき、gh api repos/cli/cli/releases/latest --jq .immutableは2026年9月16日時点でtrueを返しました。リリース機能そのものの使い方は「GitHub Releasesの使い方|リリースノート作成とタグ連携を解説」で扱っています。
Enable release immutabilityの有効化手順(リポジトリ・Organization・REST API)
リポジトリ単位の設定画面
リポジトリのトップから Settings を開き、「Releases」セクションの Enable release immutability にチェックを入れます。Settingsタブが見えない場合は、タブ列の「…」メニューから開きます。設定画面には「immutability will only apply to future releases」と注記があり、既存のリリースは対象外です。画面の文言と手順の原文は公式ドキュメント「Preventing changes to your releases」で確認できます。
Organization単位のポリシー
Organizationの Settings を開き、サイドバーの「Code, planning, and automation」にある Repository → General を選びます。「Releases」セクションの No policy ドロップダウンを All repositories または Selected repositories に変えると、配下のリポジトリに強制されます。Selected repositories を選んだ場合は、右側のボタンから対象リポジトリを指定します。
REST APIでの状態確認と有効化
多数のリポジトリに一括適用する場合はREST APIが使えます。リポジトリ単位の有効化・無効化には管理者権限、Organizationのポリシー変更にはclassic PATならadmin:orgスコープが必要です。
# 状態確認(enabled と enforced_by_owner を返す)
gh api repos/OWNER/REPO/immutable-releases
# リポジトリで有効化・無効化
gh api -X PUT repos/OWNER/REPO/immutable-releases
gh api -X DELETE repos/OWNER/REPO/immutable-releases
# Organization配下の全リポジトリに強制(all / none / selected)
gh api -X PUT orgs/ORG/settings/immutable-releases -f enforced_repositories=all
REST APIのドキュメントは「無効なら404」と記載していますが、2026年9月16日に無効のリポジトリへGETを実行すると、200で{"enabled":false,"enforced_by_owner":false}が返りました。スクリプトでは404の有無ではなくenabledの値で判定するほうが安全です。enforced_by_ownerがtrueのリポジトリは、Organization側のポリシーで強制されています。リポジトリ管理者が設定画面でチェックを外せない場合は、まずこの値を確認します。
既存リリースと無効化したときの扱い
GA時のchangelogは、有効化後の挙動を次のように定めています。
- 有効化以降に公開したリリースはすべて不変になります。
- 有効化前のリリースは「remain mutable unless you republish them」、つまり公開し直さない限り変更可能なままです。
- 無効化しても、有効だった期間に作ったリリースは不変のままです。
無効化で解除できないため、「試しに有効化して、問題があれば戻す」運用は成り立ちません。誤ったアセットを添付して公開した場合は、別のタグ名(例:v1.2.1)で出し直します。旧リリースは、タイトルとリリースノートを編集して移行先を案内したうえで残すことも、削除することもできます。どちらを選んでもバージョン番号を1つ消費するため、その前提で有効化を判断してください。
アセット追加が422エラーになる原因とdraft経由の公開手順
Cannot upload assets to an immutable releaseの発生条件
REST APIのリリース作成エンドポイントはアセットを受け付けないため、多くのリリース自動化は「リリースを公開→アセットをアップロード」の2段階で動きます。不変リリースでは公開した瞬間にアセットが固定されるため、2段階目が失敗します。softprops/action-gh-releaseのIssue #653(2025年9月1日起票)には、実際のワークフローで出た次のエラーが記録されています。
Error: Cannot upload assets to an immutable release. - https://docs.github.com/rest
このIssueは2025年12月1日にクローズされ、同アクションのv2.6.1(2026年3月16日)のリリースノートは「draft-first publish flow」を前提に書かれています。古いバージョンを固定しているワークフローは、有効化の前に更新が必要です。
gh CLIによるdraft作成・アセット添付・公開の手順
公式ドキュメントが推奨する手順は、draftで作成し、アセットをすべて添付してから公開する流れです。draftは公開前なので不変の制約を受けません。
set -e
gh release create v1.2.0 --draft --title "v1.2.0" --notes-file CHANGELOG.md
gh release upload v1.2.0 dist/app_linux_amd64.tar.gz dist/checksums.txt
gh release edit v1.2.0 --draft=false
アセットを引数に渡す1コマンドの形(gh release create v1.2.0 dist/* --notes-file CHANGELOG.md)なら、この分割は不要です。gh CLIのソース(pkg/cmd/release/create/create.go)は、アセットがあり--draft指定が無いとき、リリースをいったんdraftで作成し、全アセットのアップロード完了後に公開します。アップロードに失敗した場合は公開へ進まず、draftの削除を試みます。削除にも失敗した場合はdraftが残るため、エラー出力とリリース一覧を確認します。
アセットの生成をワークフローのジョブ間で受け渡す場合は、「GitHub Actionsのアーティファクト|受け渡しの設計と同名不可・保持期間の決め方」の設計と組み合わせ、公開ジョブを最後の1回に集約します。
リリース証明(attestation)とgh release verifyでの検証
不変リリースを公開すると、タグ・コミットSHA・各アセットのダイジェストを含むリリース証明が自動で作られます。形式はSigstore bundleで、gh CLI以外のSigstore互換ツールでも検証できるとchangelogに明記されています。リリース証明はGitHub.com限定で、GitHub Enterprise Server 3.20は非対応です。以下は、2026年9月15日に公開された不変リリース cli/cli v2.101.0 に対して、gh 2.96.0 で実行した結果です。
gh release verifyによるリリース証明の検証
$ gh release verify v2.101.0 -R cli/cli
Resolved tag v2.101.0 to sha1:0cf1092493af067646fc5f3db9421c6a6ec9c938
Loaded attestation from GitHub API
✓ Release v2.101.0 verified!
Assets
gh_2.101.0_checksums.txt sha256:f8bbc37fc5568a6a162d1a67b1e9c1afa9139f7b5a46dcde4a57bdaa0db33b60
実際の出力では、この後にdeb・rpm・tar.gz・zip・msi・pkgを含む残り21件のアセットのダイジェストが続きます。--format jsonを付けると証明の中身を取得でき、今回の出力では bundle の mediaType がapplication/vnd.dev.sigstore.bundle.v0.3+json、predicateType がhttps://in-toto.io/attestation/release/v0.2、証明書のSANがhttps://dotcom.releases.github.comでした。
検証に使うgh CLIは2.93.0以降にしてください。2.92.0以前には、gh release verify・gh release verify-asset・gh attestationの実行時に、GitHubのトークンをAPI以外のホスト(TUFリポジトリのミラー)へ送ってしまう脆弱性(CVE-2026-48501、深刻度High、2026年5月29日公開)がありました。旧版でこれらのコマンドを実行済みの場合、アドバイザリはgh CLIで使っていた認証トークンの失効、2.93.0への更新、セキュリティログと監査ログの確認を案内しています。
gh release verify-assetによるダウンロード済みファイルの照合
手元のファイルがリリースのアセットと一致するかはverify-assetで確かめます。ダウンロードしたチェックサムファイルをそのまま照合すると成功し、末尾に1行追記しただけのファイルは失敗しました(終了コード1)。
$ gh release verify-asset v2.101.0 gh_2.101.0_checksums.txt -R cli/cli
Calculated digest for gh_2.101.0_checksums.txt: sha256:f8bbc37fc5568a6a162d1a67b1e9c1afa9139f7b5a46dcde4a57bdaa0db33b60
Resolved tag v2.101.0 to sha1:0cf1092493af067646fc5f3db9421c6a6ec9c938
Loaded attestation from GitHub API
✓ Verification succeeded! gh_2.101.0_checksums.txt is present in release v2.101.0
$ echo x >> gh_2.101.0_checksums.txt
$ gh release verify-asset v2.101.0 gh_2.101.0_checksums.txt -R cli/cli
attestation for v2.101.0 does not contain subject sha256:8e1804fd58a3446638078ae853e4d3483b1b5c69cd14ac513b6a81dd7a03e9d8
リリースページの「Source code (zip)」「Source code (tar.gz)」はダウンロード要求時に生成されるため、公式ドキュメントのとおりverify-assetの対象外です。ソースを検証したい場合は、タグが解決されたコミットSHAと照合します。
リリース証明がない旧バージョンの検証失敗
cli/cliは2026年5月26日にマージしたPRで「v2.93.0以降のリリースを不変にする」とREADMEに追記しました。APIで全204件のリリースを数えると、immutable: trueはv2.93.0(2026年5月27日)以降の9件だけです。直前のv2.92.0を検証すると、証明が存在しないため次のように失敗します。
$ gh release verify v2.92.0 -R cli/cli
no attestations for tag v2.92.0 (sha1:6c470f60803784e1558b626022677c53dccb6016)
利用者側でCIにgh release verifyを組み込む場合、配布元が不変化する前のバージョンに固定していると検証ステップで止まります。検証を必須にする前に、固定しているバージョンが不変リリースかをimmutableフィールドで確認してください。依存関係の棚卸しは「SBOMとは?ソフトウェア部品表の目的・フォーマットと作成・運用の判断を解説」の手順と併せて進められます。
GitHub Actionsのメジャータグ(v1)との両立
アクションの配布では、利用者がuses: owner/action@v1と書けるよう、v1タグを最新のパッチ版へ付け替える運用が一般的です。不変リリースではタグを動かせないため、この運用が壊れるように見えますが、固定されるのはリリースに紐づいたタグだけです。
GitHub Actionsの公式ドキュメント「Using immutable releases and tags to manage your action’s releases」は、次の使い分けを示しています。
v1.0.0のような版固有のタグには、GitHubのリリースを作成して不変にする。v1やv1.1のような後で動かすタグには、リリースを作らずGitタグだけを置く。- メジャー・マイナーのタグは、最新の互換版を指すよう
git tag -fとgit push -f --tagsで付け替える。
利用者側では、動くタグ@v1ではなく完全長のコミットSHAで固定すると、タグの付け替えによる参照先の変更を防げます。SHA固定は、配布元でのImmutable Releasesの有効化とは独立した対策です。ワークフローの権限設計は「GitHub Actionsのpermissions|GITHUB_TOKENの既定権限と最小権限の設計」で扱っています。
有効化を急がないほうがよいリポジトリの条件
不変リリースは、配布物を第三者がダウンロードするOSSやCLIツールでは有効化すべき機能です。公開済みのGitHubリリースのタグやアセットが、後から差し替えられることを防げるためです。ただし、2026年3月のaxios事件で起きたnpmへの悪性バージョンの新規公開は、Immutable Releasesだけでは防げません(経緯は「axiosにマルウェア混入|サプライチェーン攻撃の全容と感染確認・安全な対処法」)。一方、次の条件に当てはまるリポジトリでは、先にワークフローを直さないと運用が止まります。
- 同じタグでリリースを作り直す運用がある:
nightlyやlatestのような固定名タグに毎回リリースを作ると、2回目以降は同名タグを再利用できません。GoReleaserは2026年4月26日の告知でnightlyビルドのタグをv2.16.0-abc1234-nightly形式の一意な名前へ変えています。 - 公開後にアセットを足すジョブがある:OSごとのビルドを並列ジョブで個別にアップロードする構成は、draftへの集約が先に必要です。
- GitHub Enterprise Serverで配布している:不変リリースは3.20.0(2026年3月17日リリース)で追加されましたが、同版のリリースノートは「Release attestations are not supported on GHES」と明記しています。改ざん防止は効く一方、
gh release verifyによる利用者側の検証は使えません。
社内限定のリポジトリで、リリースを配布経路として使っていない場合は、有効化の効果は限定的です。サプライチェーン対策全体のなかでの優先度は「サプライチェーンセキュリティとは?開発工程に組み込む防御と2026年規制対応の実装判断」で整理しています。
よくある質問
Immutable Releasesを有効化すると既存のリリースも変更できなくなりますか?
いいえ。対象は有効化以降に公開したリリースだけで、既存のリリースは公開し直さない限り変更できるままです。
不変リリースのタイトルやリリースノートは修正できますか?
修正できます。固定されるのはアセットとタグだけで、タイトル、リリースノート、プレリリース/Latestの指定は公開後も変更できます。
不変リリースを削除すれば同じタグで出し直せますか?
出し直せません。リリースを削除すればタグは削除できますが、同じタグ名は再利用できず、リポジトリを作り直しても同じです。別のバージョン番号で公開します。
Immutable Releasesを無効化すると不変のリリースは元に戻りますか?
戻りません。無効化しても、有効だった期間に公開したリリースは不変のままです。無効化の効果は、それ以降に公開するリリースにだけ及びます。
GitHub Actionsでアセットをアップロードすると422エラーになるのはなぜですか?
公開済みの不変リリースにはアセットを追加できないためです。リリースをdraftで作成してアセットを添付し、最後に公開する手順へ変えます。アセットを引数に渡すgh release createは、この順序を内部で自動的に行います。