release について
Archon のメンテナー向けリリーススキルです。dev と main を比較し、変更履歴を作成してバージョンを更新し、PR を作成します。バイナリ公開後は公開済みチェックサムで Homebrew フォーミュラを更新・同期し、導入経路を検証します。
主な機能
- 01ブランチと作業ツリーの状態を確認
- 02リリース前のバイナリスモークテストを実施
- 03コミットと差分から変更履歴を作成
- 04プロジェクトのバージョンを更新
- 05バージョンファイルとロックファイルを更新
- 06Homebrew フォーミュラを同期して導入を検証
活用シーン
- 01プロジェクトのリリースを準備
- 02dev と main のブランチ差異を検出
- 03リリースノートを作成してレビュー
- 04公開後に Homebrew のチェックサムを更新
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ coleam00/ archon --skill releaseインストール後、Claude Code で/releaseを入力して読み込まれたか確認します。
詳細
リリーススキル
dev と main を比較し、コミットから変更履歴の項目を生成して、バージョンを更新し、PR を作成してリリースします。タグのプッシュ後、リリースワークフローでバイナリのビルドが完了したら、公開された checksums.txt の実際の SHA256 値を使って Homebrew フォーミュラを更新し、coleam00/homebrew-archon tap と同期して、/test-release でエンドツーエンドのインストール手順を検証します。
⚠️ 重大 — Homebrew フォーミュラの SHA は、リリースワークフローでバイナリがビルドされるまで確定しません。
homebrew/archon.rbのversionフィールドとsha256フィールドは、同時に更新する必要があります。片方だけを更新してはいけません。正しい手順は次のとおりです。
- タグをプッシュ → リリースワークフローが実行される → バイナリがビルドされる →
checksums.txtがアップロードされる- 公開されたリリースから
checksums.txtを取得する- プラットフォームごとに SHA256 を解析する
- 新しいバージョンと新しい SHA をまとめて、1 つのコミットで
homebrew/archon.rbを更新するcoleam00/homebrew-archon/Formula/archon.rbの tap リポジトリに同期するフォーミュラの
versionフィールドだけを更新してsha256の値を更新しないと、正しいように見えてもインストール時にチェックサムが一致しない、古くて誤解を招くフォーミュラになります。以前にも発生しています(v0.3.0:バージョンは 0.3.0 に更新されましたが、SHA は v0.2.13 のままでした)。必ず両方を更新するか、どちらも更新しないでください。
プロセス
ステップ 1:状態の検証
# Must be on dev branch with clean working tree
git checkout dev
git pull origin dev --no-rebase
git status --porcelain # must be empty
git fetch origin main
dev ブランチにいない場合、または作業ツリーが汚れている場合は、明確なメッセージを表示して中止します。
続いて、dev に main が実際に含まれていることを確認します。 main に直接コミットされた変更(ドキュメントの誤字修正、ホットフィックス、前回リリースのフォーミュラのコミットなど)は、誰かが dev にマージするまで main に取り残されます。そのままではリリース PR でそれらを取り消すことになり、次のリリースにもブランチ間の差異が引き継がれます。
if git merge-base --is-ancestor origin/main origin/dev; then
echo "dev contains main — OK"
else
echo "DRIFT: commits exist on main that dev does not have:"
git log origin/dev..origin/main --oneline
echo ""
echo "Resync before releasing:"
echo " git checkout dev && git pull origin main --no-rebase && git push origin dev"
exit 1
fi
0.7.1 のリリース時に発生した例です。ae704a73 Update docs.mdx (#2155) は 0.7.0 のリリース後、main に直接コミットされ、dev には一度もマージされていませんでした。誰かが内容の不足に気づき、別の PR(#2403)で手動で先行反映したため、dev に変更内容はありましたが、そのコミット自体は含まれていませんでした。その結果、main は依然として dev の祖先ではありませんでした。ステップ 2 の前に解決し、リリース PR にこの状態を持ち込まないでください。
ステップ 1.5:コンパイル済みバイナリの事前スモークテスト(他のステップより前に必ず実施)
最初に実施する理由:モジュール初期化時のクラッシュやバンドラーのバグが
bun runではなくbun build --compileの出力でしか表面化せず、リリースに動作するバイナリが 1 つも含まれなかったことがあります。CI で検出されますが、それはタグのプッシュと GitHub Release の作成後に限られます。その時点では、すでに問題(空のリリース、壊れたreleases/latest、壊れたinstall.sh)が公開されています。ユーザーに見える変更を行う前にここで失敗させれば、影響範囲を「リリースが作成されない」に留められます。
ローカル環境のネイティブターゲットで実行します。所要時間は約 15〜30 秒で、タグとリリースを作成した後に問題を発見するよりも低コストです。
# Guard: only run this for Node/Bun projects with a CLI entry point + build-binaries script.
if [ -f scripts/build-binaries.sh ] && [ -f packages/cli/src/cli.ts ]; then
TMP_BINARY=$(mktemp)
trap "rm -f $TMP_BINARY" EXIT
# Compile for the native target only (not full cross-compile — that's CI's job).
# Match the real release flags so any bundler quirk reproduces locally.
bun build \
--compile \
--minify \
--target=bun \
--outfile="$TMP_BINARY" \
packages/cli/src/cli.ts
# Smoke test: the binary must start and exit 0 on a safe, non-interactive command.
# Use `--help` (NOT `version`). The `version` command's compiled-binary code
# path depends on BUNDLED_IS_BINARY=true, which is set by scripts/build-binaries.sh
# — but we're doing a bare `bun build --compile` here to keep the smoke fast,
# so BUNDLED_IS_BINARY is still `false`. That sends `version` down the dev
# branch of version.ts which tries to read package.json from a path that only
# exists in node_modules, producing a false-positive ENOENT. `--help` has no
# such dev/binary branch and exercises the same module-init graph we're
# actually testing. Must NOT touch network, database, or require env vars.
if ! "$TMP_BINARY" --help > /tmp/archon-preflight.log 2>&1; then
echo "ERROR: compiled binary crashed at startup"
cat /tmp/archon-preflight.log
echo ""
echo "This usually means a dependency has a module-init-time side effect that"
echo "fails in a compiled binary context (readFileSync of a path that only"
echo "exists in node_modules, etc.). Fix before cutting the release — do NOT"
echo "proceed to version bump."
exit 1
fi
# Also grep for known crash markers that exit 0 but print a fatal error
# (some module-init errors are caught by top-level try/catch but still log).
if grep -qE "Expected CommonJS module|TypeError:|ReferenceError:|SyntaxError:" /tmp/archon-preflight.log; then
echo "ERROR: compiled binary emitted a runtime error despite exit 0"
cat /tmp/archon-preflight.log
exit 1
fi
echo "Pre-flight binary smoke: PASSED"
fi
これに失敗した場合は、リリース全体を中止してください。バージョンを更新せず、CHANGELOG を変更せず、PR も作成しないでください。エラーと、失敗した出力箇所をユーザーに示して停止します。復旧手順は、フィーチャーブランチでバンドラーまたは依存関係の問題を修正し、dev にマージしてから /release を再実行することです。
このテストで検出できる一般的な失敗モード:
- Bun の
--bytecodeフラグが現在のモジュールグラフに対して壊れたバイトコードを生成する - 依存関係(SDK など)がモジュールのトップレベルで
package.jsonやその他のファイルを読み込む際、node_modules/内では正しく解決されるものの、コンパイル済みバイナリの隣では解決されないパスを使用している - 通常の
bun runでは動作するものの、ミニファイ時に失敗する循環インポート - 特殊なラッパー構造を持つ CJS を含む、新たに追加されたパッケージ
ステップ 2:スタックと現在のバージョンを検出する
プロジェクトのパッケージマネージャーとバージョンファイルを検出します。
pyproject.tomlを確認する — Python プロジェクトです。バージョンはversion = "x.y.z"に記載されています。package.jsonを確認する — Node/Bun プロジェクトです。バージョンは"version": "x.y.z"に記載されています。Cargo.tomlを確認する — Rust プロジェクトです。バージョンはversion = "x.y.z"に記載されています。go.modを確認する — Go プロジェクトです(バージョンは Git タグのみで管理され、更新するファイルはありません)。
どれも見つからない場合は、中止します:「プロジェクトのスタックを検出できませんでした — バージョンファイルが見つかりません。」
検出されたファイルから現在のバージョンを読み取ります。
ステップ 3:バージョン更新を決定する
引数に基づく更新ルール:
- 引数なし、または
patch(既定):0.1.0 -> 0.1.1 minor:0.1.3 -> 0.2.0major:0.3.5 -> 1.0.0
ステップ 4:コミットを収集する
git log main..devは使用しないでください。 このリポジトリではリリース PR を main にスカッシュマージするため、main に追加されるのはリリースごとに 1 つの新しい コミットだけで、dev の個々のコミットは含まれません。そのため、main..devにはスカッシュ されたすべてのコミットが蓄積され、リリースのたびに増えていきます。0.7.1 では、新規は 25 件だけなのに 61 件が返されました。残りの 36 件はすでに 0.7.0 でリリースされ、 その変更履歴にも記載済みでした。文字どおりに実行すると、前回のリリースの大半を 再度記録してしまいます。
実際の境界は、dev における直近の Release x.y.z コミットです。
# The previous release commit ON DEV (not the squashed one on main).
#
# The `$` anchor is load-bearing. Both commits exist on dev after Step 9's sync:
# 6c6945ce Release 0.7.1 <- dev's own version-bump commit (WANT)
# c71f7f52 Release 0.7.1 (#2435) <- main's squash, pulled back (WRONG)
# Picking the squash commit is catastrophic: its ancestry does not include dev's
# individual history, so the range balloons to the whole repo (383 commits when
# tested). Match the bare subject only.
LAST_RELEASE=$(git log origin/dev \
--grep='^Release [0-9]+\.[0-9]+\.[0-9]+$' --extended-regexp \
--format='%H' -n 1)
# First-ever release: fall back to the root commit. Without this the range
# becomes `..origin/dev`, which git resolves against HEAD and which returns
# ZERO commits — the skill would report "nothing to release" and stop.
if [ -z "$LAST_RELEASE" ]; then
LAST_RELEASE=$(git rev-list --max-parents=0 origin/dev | tail -n 1)
fi
echo "Previous release commit: $(git log -1 --oneline "$LAST_RELEASE")"
# Everything after it is genuinely new, minus the release-plumbing commits
# described below.
git log "$LAST_RELEASE"..origin/dev --oneline --no-merges \
--grep='^Release [0-9]+\.[0-9]+\.[0-9]+' \
--grep='^chore: update Homebrew formula for v' \
--grep='^chore\(homebrew\):' \
--extended-regexp --invert-grep
^chore\(homebrew\):のバックスラッシュは必須です。--extended-regexpでは、 エスケープされていない(と)はグループ化演算子です。そのため、エスケープ しない形式は文字列chorehomebrew:に一致し、フィルタリングに失敗しても気づけません。
この範囲内にある 2 種類のコミットは、変更履歴に記載する内容ではなく、リリース処理用のものであり、
上記の --invert-grep で除外されます。
Release x.y.z (#NNNN)— main のスカッシュコミット。ステップ 9 の同期によって取り込まれます。chore: update Homebrew formula for vx.y.z/chore(homebrew): …— CI ジョブと ステップ 10 のコミットです。同じ内容でブランチごとに 1 つずつ、異なる 2 つの SHA として現れる点に注意してください。
Releaseパターンは、完全なx.y.zバージョンに意図的に限定しています。単なる^Releaseでは、件名がその単語で始まる正当な機能コミットまで除外してしまいます。 実際のコミットが除外されてしまった場合は、パターンを緩めるのではなく、手動で範囲を 広げてください。
変更履歴の作成前に境界を確認してください。 前のバージョンですでに記載された内容と照合し、 重複があれば範囲が誤っています。見出しはどちらも現在のバージョンから導出し、古くならないようにしてください。
# CURRENT_VERSION is the value read in Step 2, before the bump (e.g. 0.7.1)
PREV_MAJOR_MINOR_PATCH="$CURRENT_VERSION"
# The heading immediately above the one we are about to write:
awk -v prev="## [$PREV_MAJOR_MINOR_PATCH]" '
index($0, prev) == 1 { on = 1; next }
on && /^## \[/ { exit }
on { print }
' CHANGELOG.md | grep -oE '#[0-9]{3,5}' | sort -u
コミット範囲内の PR 番号がその一覧に含まれている場合は、作業を止めて境界を再導出してください。 同じ内容を二度記載してはいけません。
[Unreleased] セクションも確認してください。 コントリビューターが手動で項目を追加している場合があります。
これらは今回のリリースに含める入力であり、境界の誤りではありません。
awk '
/^## \[Unreleased\]/ { on = 1; next }
on && /^## \[/ { exit }
on { print }
' CHANGELOG.md
新しいコミットがない場合は、中止します:「リリース対象がありません — dev は main と同期しています。」
ステップ 5:変更履歴の項目を作成する
コミットメッセージと実際の差分(git diff "$LAST_RELEASE"..origin/dev)を読んで、何が変更されたかを把握します。
生のコミットの件名よりも、PR のタイトルと ## Summary セクションを優先してください。
これらには、変更履歴の項目に必要なユーザー向けの問題が記載されています。
gh pr view <N> --repo coleam00/Archon --json number,title,body,closingIssuesReferences
ステップ 4 の [Unreleased] の項目ですでに説明されている変更は除き、それ以外の変更についてのみ項目を作成します。各変更が一度だけ記載されるようにしてください。手書きの項目では、PR ではなく、その PR がクローズする issue(closingIssuesReferences)が参照されていることもあれば、何も参照されていないこともあります。番号ではなく、項目の内容で判断してください。手書きの項目の要点(移行テーブルや正確な詳細など)はそのまま維持し、表現と配置だけを整えてください。項目が PR に対応しているか判断できない場合は、ステップ 7 で確認してください。
Keep a Changelog のセクションに次の順序で分類します(空のセクションは省略):
- Breaking — ユーザーが依存している可能性のある既存の動作を破壊する変更
- Security — 脆弱性の修正と、セキュリティに関係する強化
- Added — 新機能、新しいファイル、新しい機能
- Changed — 既存の動作への変更
- Deprecated — 引き続き利用可能だが、非推奨、または削除が予定されている機能
- Removed — 削除された機能やコード
- Fixed — バグ修正
執筆ルール:
- 人が書いたように、明確で簡潔なユーザー向けの言葉で項目を記述します
- コミットメッセージをそのままコピーするのではなく、適切な変更履歴の項目として書き直します
- 関連するコミットは、適切な場合に 1 つの項目にまとめます
- PR 番号がある場合は、括弧内に記載します:
(#12) - 各項目は、何が変更されたかを表す名詞または動名詞で始めます
- 内部向けの変更(CI の調整、誤字修正など)は、動作に影響しない限り省略します
- セクション間には空行を 1 行入れます
ステップ 6:ファイルを更新する
-
バージョンファイル — バージョンを新しい値に更新します:
package.json:"version": "x.y.z"を更新しますpyproject.toml:version = "x.y.z"を更新しますCargo.toml:version = "x.y.z"を更新します
-
ワークスペースのバージョン同期(モノレポのみ):
scripts/sync-versions.shが存在する場合は、bash scripts/sync-versions.shを実行して、すべてのpackages/*/package.jsonのバージョンをルートのバージョンに合わせて同期します。このスクリプトはbun.lockも更新するため、ワークスペースのバージョンはロックファイルにも反映されます。
-
ロックファイルの更新(スタックに応じて実施)— ステップ 2 のスクリプトですでにロックファイルが更新されている場合は、該当する行を省略します:
package.json+bun.lock:bun installを実行しますpackage.json+package-lock.json:npm install --package-lock-onlyを実行しますpyproject.toml+uv.lock:uv lock --quietを実行しますCargo.toml:cargo update --workspaceを実行します
-
CHANGELOG.md—[Unreleased]の下に、[Unreleased]の項目とステップ 5 で作成した項目から新しいバージョンのセクションを 1 つ追加します。ステップ 5 の順序に従い、空でないカテゴリごとに### <Category>見出しを 1 つ設けます。
## [x.y.z] - YYYY-MM-DD
One-line summary of the release.
### Breaking
- Entry one (#PR)
### Added
- Entry one (#PR)
- Entry two (#PR)
### Fixed
- Entry one (#PR)
### Breaking は、1 行の要約の直後、最初に配置する必要があります。 ステップ 9 のリリースワークフローでは、このセクションがそのまま GitHub のリリース本文に使用されるため、このファイル内のセクション順序によって、リリースページで破壊的変更が最初に表示されるようになります。
[Unreleased] の見出しの下には何も記載しないでください。
ステップ 7:レビューを依頼する
ユーザーに次の内容を提示します:
- 検出したスタックとバージョンファイル
- バージョンの更新(旧バージョン -> 新バージョン)
- 追加する変更履歴セクション全体
- 含めるコミットの一覧
- PR に対応しているか判断できなかった
[Unreleased]の項目があれば、該当する PR の横に記載
次のように尋ねます:「問題なさそうですか?コミットして PR を作成します。」
ステップ 8:コミットと PR
ユーザーの承認を得てから実行します:
# Stage version file, workspace packages, lockfile, and changelog
git add <version-file> packages/*/package.json <lockfile> CHANGELOG.md
git commit -m "Release x.y.z"
# Push dev
git push origin dev
# Create PR: dev -> main
gh pr create --base main --head dev \
--title "Release x.y.z" \
--body "$(cat <<'EOF'
## Release x.y.z
{changelog section content}
---
Merging this PR releases x.y.z to main.
EOF
)"
PR の URL をユーザーに返します。
ステップ 9:マージ後のタグ付け、リリース、同期
PR がマージされた後(ユーザーによるマージ、または gh pr merge によるマージのどちらでも):
# Fetch the merge commit on main
git fetch origin main
# Tag the merge commit
git tag vx.y.z origin/main
git push origin vx.y.z
# Create a GitHub Release from the tag. --generate-notes is a placeholder: the
# push above already triggered .github/workflows/release.yml, and once its
# `release` job finishes building binaries (5-10 min), its "Build release
# notes" step overwrites this release's body with CHANGELOG.md's own
# `## [x.y.z]` section (Breaking first, per Step 6) plus install instructions.
# That job is the single source of truth for the body, so it does not matter
# that this step's placeholder is throwaway, or which of the two finishes
# first — do NOT hand-craft curated notes here, they would just be discarded.
gh release create vx.y.z --title "vx.y.z" --generate-notes
# Sync dev with main so both branches are identical
git checkout dev
git pull origin main --no-rebase
git push origin dev
# Verify the sync actually converged — do not assume it did. FAIL CLOSED:
# continuing past a failed sync publishes a formula and tap from a tree that
# does not match what was released.
git fetch origin
if git merge-base --is-ancestor origin/main origin/dev; then
echo "dev contains main — OK"
else
echo "STILL DIVERGED — stranded on main:"
git log origin/dev..origin/main --oneline
exit 1
fi
--no-rebaseは必須であり、省略できません。 これを付けないと、pull.rebaseを設定していないマシンでは、git がfatal: Need to specify how to reconcile divergent branchesというエラーで中断します。また、このステップで実際に必要なマージ動作に固定するためにも必要です。そうしないと、pull.rebase=trueの設定によって dev の履歴が書き換えられてしまい、これは以下の警告で禁じていることそのものです。
2 回実行することを想定してください。 作業中に CI の
update-homebrewジョブが独自の formula コミットを dev にプッシュするため、ここでのgit push origin dev(およびステップ 10 での同じコマンド)はUpdates were rejectedで拒否されることがあります。これは正常な動作です。git pull origin dev --no-rebaseを実行してから、もう一度プッシュしてください。0.7.1 では、このステップもステップ 10 も 2 回目の実行が必要でした。
重要: この同期により、dev に main のマージコミットが反映されます。これを行わないと、dev と main の履歴が分岐します。CI の
update-homebrewジョブは formula コミットを dev にプッシュするだけで、PR のマージコミットを dev に反映することはありません。この手動のgit pull origin main --no-rebaseによって、dev にマージコミットが反映されます。
この同期に
git pull origin main --ff-onlyやgit reset --hard origin/mainを使用しないでください。スクワッシュマージをまたいでのファストフォワードは不可能です。main のスクワッシュコミットは dev のリリースコミットとは SHA が異なるため、dev を main にファストフォワードすることはできません。また、dev を main にリセットすると dev の履歴が書き換えられ、開いているすべての PR のマージベースが元のコミットから切り離され、差分が数千行に膨れ上がります(v0.3.10 のリリースで確認済み:dev でgit reset --hard origin/mainを実行した後、PR の差分は+80/-1から+6626/-300になりました)。上記の通常のgit pull origin main --no-rebaseは、dev 上に通常のマージコミットを作成します。開いている PR の整合性を保つためのコストとして、dev のgit logにマージの履歴が表示されるのは適切です。復旧作業中にマージでhomebrew/archon.rbの競合が発生した場合は、git checkout origin/main -- homebrew/archon.rbで解決してください(注意:mainではなくorigin/mainを指定します。リリース時はgit push origin dev:mainによってローカルブランチをファストフォワードせずにプッシュすることが多いため、ローカルの main は古い状態になっていることがあります)。
GitHub Release は git タグとは別のものです。GitHub Release がないと、リポジトリの Releases ページにリリースが表示されません。必ず作成してください。
ユーザー自身が PR をマージしてから戻ってきた場合も、タグ付け、リリース、同期を行うかどうか、引き続き提案してください。
ステップ 10:リリースワークフローを待ち、Homebrew Formula を更新する
注:
.github/workflows/release.ymlのupdate-homebrewCI ジョブは、リリースジョブの後に自動的に実行され、Formula の更新と dev へのプッシュ(ステップ 10 の一部)を処理します。 ステップ 11(coleam00/homebrew-archonへの tap の同期)は常に手動です。ステップ 10 を手動で実行する前に、Actions タブを確認してください。
タグをプッシュすると、.github/workflows/release.yml がプラットフォーム別のバイナリをビルドし、GitHub リリースにアップロードします。この処理には 5~10 分かかります。これらのバイナリが生成されるまでは、Homebrew Formula の SHA256 値は分かりません。
リリースにすべてのアセットが表示されるまで待ちます:
echo "Waiting for release workflow to finish uploading binaries..."
WORKFLOW_FAILED=0
for i in {1..30}; do
ASSET_COUNT=$(gh release view "vx.y.z" --repo coleam00/Archon --json assets --jq '.assets | length')
# Expect 7 assets: 5 binaries (darwin-arm64, darwin-x64, linux-arm64, linux-x64, windows-x64.exe) + archon-web.tar.gz + checksums.txt
if [ "$ASSET_COUNT" -ge 7 ]; then
echo "All $ASSET_COUNT assets uploaded"
break
fi
# Short-circuit: if the release workflow itself has failed, stop waiting.
# Hanging for 15 min when CI already crashed just delays the recovery path.
WORKFLOW_STATUS=$(gh run list --workflow release.yml --event push --limit 1 --json conclusion,status --jq '.[0] | "\(.status)|\(.conclusion)"')
if [[ "$WORKFLOW_STATUS" == "completed|failure" ]]; then
echo "Release workflow FAILED — no point waiting longer"
WORKFLOW_FAILED=1
break
fi
echo " Assets so far: $ASSET_COUNT/7 — waiting 30s (attempt $i/30)..."
sleep 30
done
if [ "$WORKFLOW_FAILED" -eq 1 ] || [ "$ASSET_COUNT" -lt 7 ]; then
# Triage: rerun once in case it's transient, then check again.
RUN_ID=$(gh run list --workflow release.yml --event push --limit 1 --json databaseId --jq '.[0].databaseId')
echo "Release workflow failed on run $RUN_ID. Rerunning failed jobs once to confirm..."
gh run rerun "$RUN_ID" --failed
gh run watch "$RUN_ID" --exit-status --interval 30 || true
# Re-check asset count + run status after rerun.
ASSET_COUNT=$(gh release view "vx.y.z" --repo coleam00/Archon --json assets --jq '.assets | length')
if [ "$ASSET_COUNT" -ge 7 ]; then
echo "Rerun succeeded — all assets now present"
else
echo ""
echo "===== DETERMINISTIC CI FAILURE ====="
echo "The release workflow failed on two consecutive runs. This is NOT a flake."
echo "The tag and release exist but have no (or incomplete) assets."
echo ""
echo "install.sh and similar 'releases/latest' paths are now 404-ing."
echo "Proceeding with Homebrew/tap sync would publish a formula pointing at"
echo "missing or inconsistent binaries."
echo ""
echo "Jump to the 'Recovery: deterministic release CI failure' section at the"
echo "bottom of this skill and execute it. Do NOT continue past this point."
exit 1
fi
fi
checksums.txt を取得し、SHA256 値を抽出します:
TMP_DIR=$(mktemp -d)
gh release download "vx.y.z" --repo coleam00/Archon --pattern "checksums.txt" --dir "$TMP_DIR"
DARWIN_ARM64_SHA=$(awk '/archon-darwin-arm64$/ {print $1}' "$TMP_DIR/checksums.txt")
DARWIN_X64_SHA=$(awk '/archon-darwin-x64$/ {print $1}' "$TMP_DIR/checksums.txt")
LINUX_ARM64_SHA=$(awk '/archon-linux-arm64$/ {print $1}' "$TMP_DIR/checksums.txt")
LINUX_X64_SHA=$(awk '/archon-linux-x64$/ {print $1}' "$TMP_DIR/checksums.txt")
# Sanity check — all four must be present and non-empty
for var in DARWIN_ARM64_SHA DARWIN_X64_SHA LINUX_ARM64_SHA LINUX_X64_SHA; do
if [ -z "${!var}" ]; then
echo "ERROR: $var is empty — checksums.txt may be malformed"
cat "$TMP_DIR/checksums.txt"
exit 1
fi
done
rm -rf "$TMP_DIR"
メインリポジトリの homebrew/archon.rb を、バージョンと SHA を含めて一括で更新します:
以下の正確なテンプレートを使って Formula ファイルを書き換えます。sed でファイルを直接編集しないでください。部分的な更新のリスクを完全になくすため、ファイル全体をこのテンプレートから再生成してください。
cat > homebrew/archon.rb << EOF
# Homebrew formula for Archon CLI
# To install: brew install coleam00/archon/archon
#
# This formula downloads pre-built binaries from GitHub releases.
# For development, see: https://github.com/coleam00/Archon
class Archon < Formula
desc "Remote agentic coding platform - control AI assistants from anywhere"
homepage "https://github.com/coleam00/Archon"
version "x.y.z"
license "MIT"
on_macos do
on_arm do
url "https://github.com/coleam00/Archon/releases/download/v#{version}/archon-darwin-arm64"
sha256 "${DARWIN_ARM64_SHA}"
end
on_intel do
url "https://github.com/coleam00/Archon/releases/download/v#{version}/archon-darwin-x64"
sha256 "${DARWIN_X64_SHA}"
end
end
on_linux do
on_arm do
url "https://github.com/coleam00/Archon/releases/download/v#{version}/archon-linux-arm64"
sha256 "${LINUX_ARM64_SHA}"
end
on_intel do
url "https://github.com/coleam00/Archon/releases/download/v#{version}/archon-linux-x64"
sha256 "${LINUX_X64_SHA}"
end
end
def install
binary_name = case
when OS.mac? && Hardware::CPU.arm?
"archon-darwin-arm64"
when OS.mac? && Hardware::CPU.intel?
"archon-darwin-x64"
when OS.linux? && Hardware::CPU.arm?
"archon-linux-arm64"
when OS.linux? && Hardware::CPU.intel?
"archon-linux-x64"
end
bin.install binary_name => "archon"
end
test do
# Basic version check - archon version should exit with 0 on success
assert_match version.to_s, shell_output("#{bin}/archon version")
end
end
EOF
Formula の更新を main にコミットしてから、dev に同期します:
git checkout main
git pull origin main --no-rebase
git add homebrew/archon.rb
git commit -m "chore(homebrew): update formula to vx.y.z"
git push origin main
# Sync dev with main so the formula update is on both branches
git checkout dev
git pull origin main --no-rebase
git push origin dev # may be rejected — see below
CI の update-homebrew ジョブと競合します。 このジョブは、あなたが main に同じ変更をコミットしている間に、Formula のコミット(例:chore: update Homebrew formula for vx.y.z)を dev に直接書き込みます。どちらも同じ 4 行の sha256 を変更するため、上記のプッシュは Updates were rejected というエラーで失敗します:
git pull origin dev --no-rebase # merges CI's commit; usually resolves clean
git push origin dev
競合のないマージでも、SHA が正しいことの証明にはなりません。 同じ行を編集する 2 つのコミットは、競合せずにマージされても、誤った値が残ることがあります。プッシュする前に、公開済みのチェックサムと照合してください。値が誤っているとすべてのユーザーのインストールが失敗する、唯一のファイルです:
VERSION=x.y.z # without the leading v
gh release download "v$VERSION" --repo coleam00/Archon --pattern checksums.txt --dir /tmp/rel
fail=0
# Assert the formula version matches the release. A stale version points every
# URL at the wrong release while carrying the new digests.
formula_version=$(awk -F'"' '/^[[:space:]]*version "/ {print $2; exit}' homebrew/archon.rb)
if [ "$formula_version" != "$VERSION" ]; then
echo " BAD version: formula says '$formula_version', release is '$VERSION'"
fail=1
fi
# Compare each digest against the sha256 that FOLLOWS ITS OWN url line. A naive
# `grep -q "$digest" formula` only asks whether the value appears anywhere, so
# two platforms with swapped hashes both pass — and an empty digest degenerates
# to `grep -q ""`, which matches every file.
for p in archon-darwin-arm64 archon-darwin-x64 archon-linux-arm64 archon-linux-x64; do
real=$(awk -v p="$p" '$2 ~ "(^|/)" p "$" {print $1}' /tmp/rel/checksums.txt | head -1)
if ! printf '%s' "$real" | grep -qE '^[0-9a-f]{64}$'; then
echo " BAD $p: no 64-char digest in checksums.txt (got '$real')"
fail=1
continue
fi
in_formula=$(awk -v p="$p" '
index($0, "/" p "\"") { seen = 1; next }
seen && /sha256 "/ { gsub(/.*sha256 "|".*/, ""); print; exit }
' homebrew/archon.rb)
if [ "$in_formula" = "$real" ]; then
echo " OK $p"
else
echo " BAD $p: formula has '$in_formula', release has '$real'"
fail=1
fi
done
[ "$fail" -eq 0 ] || { echo "formula verification FAILED — do not push"; exit 1; }
echo "formula verified against v$VERSION checksums"
すべての行に OK が表示され、バージョンも一致している必要があります。BAD が1つでもあれば、手作業で編集せず、ステップ 10 のテンプレートから Formula を再生成してください。手作業で編集すると、ダイジェストが誤ったプラットフォームに割り当てられる原因になります。
最後に、同期が完了していることを確認します。フェイルクローズにしてください。ステップ 11 ではユーザーがインストールする tap に公開するため、ツリーが同期されていない状態で先に進まないでください:
git fetch origin
converged=1
git merge-base --is-ancestor origin/main origin/dev \
|| { echo "DIVERGED: dev does not contain main"; converged=0; }
stranded=$(git log origin/dev..origin/main --oneline | wc -l | tr -d ' ')
[ "$stranded" -eq 0 ] || { echo "STRANDED: $stranded commit(s) on main not on dev:"; \
git log origin/dev..origin/main --oneline; converged=0; }
[ "$converged" -eq 1 ] || { echo "sync incomplete — resolve before Step 11"; exit 1; }
echo "branches converged — OK"
ステップ 11:Homebrew Tap リポジトリを同期する
coleam00/homebrew-archon リポジトリには、ユーザーが brew tap coleam00/archon && brew install coleam00/archon/archon を実行したときに Homebrew が読み込む実際の tap Formula が置かれています。coleam00/Archon/homebrew/archon.rb ファイルが正となるテンプレートで、coleam00/homebrew-archon/Formula/archon.rb ファイルがユーザーの実際のインストール元です。これらを同期しておく必要があります。
TAP_DIR=$(mktemp -d)
git clone [email protected]:coleam00/homebrew-archon.git "$TAP_DIR"
cp homebrew/archon.rb "$TAP_DIR/Formula/archon.rb"
cd "$TAP_DIR"
if git diff --quiet; then
echo "Tap formula already matches — no sync needed"
else
git add Formula/archon.rb
git commit -m "chore: sync formula to vx.y.z"
git push origin main
fi
cd -
rm -rf "$TAP_DIR"
git clone が権限エラーで失敗する場合、リリーススキルを実行しているユーザーには coleam00/homebrew-archon へのプッシュ権限がありません。リポジトリのオーナーにプッシュ権限をリクエストするか、GitHub の Web UI から手動で同期するよう依頼してください。このステップを黙って省略しないでください。tap の同期が完了するまでは、リリースは完了していません。
ステップ 12:リリースをエンドツーエンドで検証する
フォーミュラの同期後、最終確認として、Homebrew 経由でリリース済みバイナリを実際にインストールし、スモークテストを実行します。test-release スキルを使用してください。
/test-release brew x.y.z
以下を実行します。
brew tap coleam00/archon && brew install coleam00/archon/archonでインストールします- バイナリが正しいバージョンと
Build: binaryを報告することを確認します - 同梱されたワークフローが読み込まれることを確認します
- SDK の起動パスが機能することを確認します(最小限の archon-investigate の実行)
- 環境変数漏えいガードが、漏えいする .env から機密キーを除去することを確認します(テスト 4)
- 正常にアンインストールできることを確認します
- PASS/FAIL レポートを生成します
/test-release brew が失敗した場合、リリースの告知準備はできていません。 問題の内容に応じてホットフィックスの課題を作成し、修正を含む x.y.z+1 をリリースして、このスキルを再実行してください。test-release に失敗するリリースを告知してはいけません。
curl のインストール経路も確認するため、/test-release curl-mac x.y.z も実行してください。2 つのインストール経路では、テストする内容が少し異なります(Homebrew は tap のフォーミュラをテストし、curl はリリースの install.sh とチェックサムを確認します)。ユーザーが安定してインストールできるようにするには、どちらも正常に動作する必要があります。
利用可能な VPS がある場合は、Linux バイナリを検証するために /test-release curl-vps x.y.z <vps-target> も実行してください。
復旧:決定的なリリース CI の失敗
ステップ 10 でワークフローが 2 回連続して失敗したため、ここに進みました。タグ vx.y.z はプッシュ済みで、GitHub リリースも存在しますが、アセットが欠落しているか不完全です。現在、install.sh を実行すると毎回 releases/latest がこの壊れたリリースを指し、ダウンロード時に 404 が発生します。ステップ 10 のアトミックなフォーミュラ更新がブロックされたため、Homebrew ユーザーには影響ありません。
成功を期待して、リリースワークフローを 3 回目に再実行してはいけません。 2 回再現した失敗はコードのバグです。修正するには、コードをリリースする必要があります。
直ちに行う緩和策(install.sh を復旧する)
GitHub リリースを削除し、releases/latest が前のバージョンを指すようにします。git タグはそのまま残してください。タグの不変性は重要であり、そもそもそのタグを指すリリース済みアーティファクトはありません。
gh release delete "vx.y.z" --yes
# Do NOT delete the tag:
# git push --delete origin vx.y.z ← do not run
# Tag stays so git history records the attempt; no release means no assets
# means releases/latest resolves to the prior working release.
次を確認します。
gh api repos/coleam00/Archon/releases/latest --jq '.tag_name'
# should now print the prior version (e.g. v0.3.6), not vx.y.z
診断
リリースワークフローのログから、失敗したターゲットと失敗した段階(コンパイル、スモークテスト、アップロード)を特定できます。
gh run list --workflow release.yml --limit 2 --json databaseId,conclusion
gh run view <RUN_ID> --log-failed
よくある原因:
- バンドラー/バイトコードのバグ — Bun の
--bytecodeが、現在のモジュールグラフに対して無効な出力を生成します。症状:バイナリの起動時にTypeError: Expected CommonJS module to have a function wrapperが発生します。過去には、新しい依存関係の CJS/ESM の形式が--bytecodeと干渉して起きたことがあり、フラグを削除するか、問題のモジュールを遅延インポートすることで解決しました。 - モジュール初期化時のクラッシュ — 依存関係が、
node_modules/内には存在するもののコンパイル済みバイナリの隣には存在しないパスを使って、モジュールのトップレベルでreadFileSync('package.json')などを実行します。症状:バイナリのすべてのサブコマンドが直ちにクラッシュし、通常、エラーにはprocess.execPathの隣にあるファイルが見つからないことが示されます。実際に使用するコードパスの背後に依存関係の遅延インポートを配置して修正します。 - Windows でのスモークテストのタイムアウト — コードのバグではなく、Windows ランナーの動作が遅いことが原因です。1 回再実行し、再発する場合はテストのタイムアウトを延長してください。
ステップ 1.5 では、ユーザーに見えるステップを実行する前に、コンパイル済みバイナリのローカルスモークテストを実行するようになりました。上記の失敗がローカルでも再現する場合は、原因を特定できたことになります。再現しない場合は、プラットフォーム固有のバグ(Windows のクロスコンパイル、Linux の glibc など)であり、CI ログが必要です。
修正して、次のパッチバージョンとして再リリースする
vx.y.zを再利用しないでください。 修正を含めてvx.y.(z+1)(または必要に応じて次のマイナーバージョン)をリリースします。理由:
- タグの不変性:
vx.y.zはすでにGitの履歴とリリースキャッシュに記録されています - SemVerの明確さ:ビットが変更されたら、ユーザーやツールから新しいバージョン番号が見えるようにする必要があります
- 監査証跡:「v0.3.7はリリースされたが、配布バイナリはなかった。v0.3.8がを含む最初のリリースである」とするほうが、v0.3.7を書き換えるより明確です
手順:
- devから修正ブランチを作成し、修正を実装して、devにPRを作成し、マージします。
/releaseを再実行します(次のパッチバージョン(例:0.3.8)に自動的に更新されます)。- ステップ1.5のリリース前スモークテストで、修正が実際には問題を解決していない場合、同じバグがローカルで検出されます。タグを付ける前に、テストに合格するまで修正を繰り返します。
ホットフィックスリリースのCHANGELOGへの注記
新しいリリースのCHANGELOGに、壊れていた以前のバージョンを示す行を含めて、該当タグにバイナリアーティファクトがない理由をユーザーに説明します。
### Fixed
- **First release with working compiled binaries after vx.y.z's <bug>.** vx.y.z was tagged but its binary smoke test failed deterministically (see RUN_ID in CI history). The tag is preserved for history; this release (vx.y.(z+1)) is the first with shipped binaries. `install.sh` and Homebrew were never updated to vx.y.z, so users were not exposed to the broken state.
してはいけないこと
- タグを強制プッシュしたり、書き換えたりしないでください。 タグが作成された時点で、そのSHAが公開された確約になります。タグを削除して別のSHAで作り直すことはタグの偽装であり、元のタグをキャッシュした下流の利用者に影響します。
- 「壊れたリリースにバイナリを追加でプッシュする」ために、この復旧手順を省略しないでください。 リリースには特定のコミットSHAが紐付いています。新しいSHAからビルドしたバイナリをアップロードすると、バイナリとソースのずれが生じ、後からの診断が難しくなります。
- v0.3.(z+1)のリリースが完了する前に、Homebrewのフォーミュラを更新しないでください。 フォーミュラは、7つすべてのアセットがアップロードされ、
/test-release brewに合格したバージョンを常に参照するようにしてください。
重要なルール
- 強制プッシュは絶対にしないでください。
git log main..devから変更履歴を決して導き出さないでください。 main はスカッシュマージするため、この範囲には以前にリリースされたすべてのコミットが蓄積され、リリースのたびに増えていきます(0.7.1 では実際の 25 件に対して 61 件)。dev の直近のRelease x.y.zコミットを境界として使い、前バージョンの変更履歴エントリーとも照合してください。両方に同じ PR 番号が含まれている場合、境界が間違っています。git pullには必ず--no-rebaseを指定してください。 何も指定しないと、pull.rebaseが設定されていない場合にNeed to specify how to reconcile divergent branchesというエラーで中断します。また、pull.rebase=trueの設定は dev の履歴を書き換えてしまいます。- 同期が収束したと決めつけず、必ず確認してください。
同期の各手順後に
git merge-base --is-ancestor origin/main origin/devを実行してください。そうしないと、main に直接行われたコミットが気付かれないまま取り残されます。 homebrew/archon.rbのマージが競合なく完了しても、決して信頼しないでください。 CI のupdate-homebrewジョブは、あなたが main で編集するのと同じsha256の行を dev で編集します。マージが成功しても、内容が間違っている場合があります。プッシュ前に、4 つの値を公開済みのchecksums.txtと比較してください。- コミット範囲と矛盾するバージョンの更新は指摘してください。
patchが指定されているのに、範囲にfeat:コミットやユーザー向けの CLI 機能の追加が含まれている場合は、Step 7 のレビューでその旨を伝え、ユーザーの判断を仰いでください。機能をパッチとして黙ってリリースしてはいけません。 - Step 1.5(コンパイル済みバイナリの事前スモークテスト)を絶対に省略しないでください。 Bun/Node プロジェクトでバイナリをビルドするスクリプトがある場合、バージョン更新、PR、タグ付けの前に
bun build --compileのスモークテストを実行します。省略すると、バンドラーの不具合やモジュール初期化時のクラッシュがタグのプッシュ後に初めて発覚します。その時点では、すでにすべてのユーザーに対してreleases/latestが 404 になっています。失敗をローカルで発見するために、約 30 秒かけてテストを実行します。 - Step 1.5 に失敗した場合は、リリースを中止し、フィーチャーブランチで根本的な問題を修正してください。「これを飛ばして」CI で同じ問題が再現されないことを期待してはいけません。
- レビューステップは絶対に省略しないでください。コミット前に必ず変更履歴を提示してください。
- コミットに
Co-Authored-By: Claudeや AI による作成を示す記載を絶対に含めないでください。 - ユーザーから求められない限り、変更履歴のエントリーに絵文字を絶対に追加しないでください。
- ユーザーが更新種別を指定せずに「ship it」と言った場合は、デフォルトで patch とします。
- コミットメッセージは
Release x.y.zのみとし、簡潔にしてください。 homebrew/archon.rbのバージョンフィールドを更新する場合は、必ずsha256の値も更新してください。 両方を一体として更新する必要があります。正しい SHA が得られるのは、リリースワークフローでバイナリのビルドが完了した後だけです。Step 10 を参照してください。バージョンフィールドだけを更新すると、正しいように見えても古いフォーミュラができ、インストール時にチェックサムが一致しなくなります。- Step 11(tap の同期)を絶対に省略しないでください。
coleam00/Archon/homebrew/archon.rbファイルはテンプレートにすぎません。ユーザーがインストールに使うのはcoleam00/homebrew-archon/Formula/archon.rbです。一方だけを更新すると、ユーザーに古いデータや誤ったデータが届きます。 /test-release brewに失敗したリリースを絶対に告知しないでください。 インストールはできても初回実行時にクラッシュするリリースは、リリースがない場合よりも悪く、ユーザーの信頼を損ないます。リリースの検証に失敗した場合は、リリースの存在を告知する前にホットフィックスをリリースしてください。