mcpField
R
release

release

github.com/coleam00開発23.8k275 回インストール今日更新
リポジトリ

release について

Archon のメンテナー向けリリーススキルです。dev と main を比較し、変更履歴を作成してバージョンを更新し、PR を作成します。バイナリ公開後は公開済みチェックサムで Homebrew フォーミュラを更新・同期し、導入経路を検証します。

主な機能

  1. 01ブランチと作業ツリーの状態を確認
  2. 02リリース前のバイナリスモークテストを実施
  3. 03コミットと差分から変更履歴を作成
  4. 04プロジェクトのバージョンを更新
  5. 05バージョンファイルとロックファイルを更新
  6. 06Homebrew フォーミュラを同期して導入を検証

活用シーン

  1. 01プロジェクトのリリースを準備
  2. 02dev と main のブランチ差異を検出
  3. 03リリースノートを作成してレビュー
  4. 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 フィールドは、同時に更新する必要があります。片方だけを更新してはいけません。

正しい手順は次のとおりです。

  1. タグをプッシュ → リリースワークフローが実行される → バイナリがビルドされる → checksums.txt がアップロードされる
  2. 公開されたリリースから checksums.txt を取得する
  3. プラットフォームごとに SHA256 を解析する
  4. 新しいバージョンと新しい SHA をまとめて、1 つのコミットで homebrew/archon.rb を更新する
  5. 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:スタックと現在のバージョンを検出する

プロジェクトのパッケージマネージャーとバージョンファイルを検出します。

  1. pyproject.toml を確認する — Python プロジェクトです。バージョンは version = "x.y.z" に記載されています。
  2. package.json を確認する — Node/Bun プロジェクトです。バージョンは "version": "x.y.z" に記載されています。
  3. Cargo.toml を確認する — Rust プロジェクトです。バージョンは version = "x.y.z" に記載されています。
  4. go.mod を確認する — Go プロジェクトです(バージョンは Git タグのみで管理され、更新するファイルはありません)。

どれも見つからない場合は、中止します:「プロジェクトのスタックを検出できませんでした — バージョンファイルが見つかりません。」

検出されたファイルから現在のバージョンを読み取ります。

ステップ 3:バージョン更新を決定する

引数に基づく更新ルール:

  • 引数なし、または patch(既定):0.1.0 -> 0.1.1
  • minor:0.1.3 -> 0.2.0
  • major: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 のセクションに次の順序で分類します(空のセクションは省略):

  1. Breaking — ユーザーが依存している可能性のある既存の動作を破壊する変更
  2. Security — 脆弱性の修正と、セキュリティに関係する強化
  3. Added — 新機能、新しいファイル、新しい機能
  4. Changed — 既存の動作への変更
  5. Deprecated — 引き続き利用可能だが、非推奨、または削除が予定されている機能
  6. Removed — 削除された機能やコード
  7. Fixed — バグ修正

執筆ルール:

  • 人が書いたように、明確で簡潔なユーザー向けの言葉で項目を記述します
  • コミットメッセージをそのままコピーするのではなく、適切な変更履歴の項目として書き直します
  • 関連するコミットは、適切な場合に 1 つの項目にまとめます
  • PR 番号がある場合は、括弧内に記載します:(#12)
  • 各項目は、何が変更されたかを表す名詞または動名詞で始めます
  • 内部向けの変更(CI の調整、誤字修正など)は、動作に影響しない限り省略します
  • セクション間には空行を 1 行入れます
ステップ 6:ファイルを更新する
  1. バージョンファイル — バージョンを新しい値に更新します:

    • package.json:"version": "x.y.z" を更新します
    • pyproject.toml:version = "x.y.z" を更新します
    • Cargo.toml:version = "x.y.z" を更新します
  2. ワークスペースのバージョン同期(モノレポのみ):

    • scripts/sync-versions.sh が存在する場合は、bash scripts/sync-versions.sh を実行して、すべての packages/*/package.json のバージョンをルートのバージョンに合わせて同期します。このスクリプトは bun.lock も更新するため、ワークスペースのバージョンはロックファイルにも反映されます。
  3. ロックファイルの更新(スタックに応じて実施)— ステップ 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 を実行します
  4. 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:レビューを依頼する

ユーザーに次の内容を提示します:

  1. 検出したスタックとバージョンファイル
  2. バージョンの更新(旧バージョン -> 新バージョン)
  3. 追加する変更履歴セクション全体
  4. 含めるコミットの一覧
  5. 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-homebrew CI ジョブは、リリースジョブの後に自動的に実行され、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を書き換えるより明確です

手順:

  1. devから修正ブランチを作成し、修正を実装して、devにPRを作成し、マージします。
  2. /releaseを再実行します(次のパッチバージョン(例:0.3.8)に自動的に更新されます)。
  3. ステップ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 に失敗したリリースを絶対に告知しないでください。 インストールはできても初回実行時にクラッシュするリリースは、リリースがない場合よりも悪く、ユーザーの信頼を損ないます。リリースの検証に失敗した場合は、リリースの存在を告知する前にホットフィックスをリリースしてください。

コメント