Loading...
Loading...
Create a release from dev branch. Generates changelog entries from commits, bumps version, and creates a PR to main. TRIGGERS - Use this skill when user says: - "/release" - create a patch release (default) - "/release minor" - create a minor release - "/release major" - create a major release - "make a release", "cut a release", "ship it", "release to main"
npx skill4agent add coleam00/archon releasechecksums.txtcoleam00/homebrew-archon/test-release⚠️ CRITICAL — Homebrew formula SHAs cannot be known until after the release workflow builds binaries.Thefield inversionand thehomebrew/archon.rbfields must be updated atomically. Never update one without the other.sha256The correct sequence is:
- Tag is pushed → release workflow fires → binaries built →
uploadedchecksums.txt- Fetch
from the published releasechecksums.txt- Parse the SHA256 per platform
- Update
with the new version AND the new SHAs in a single commithomebrew/archon.rb- Sync to the
tap repocoleam00/homebrew-archon/Formula/archon.rbUpdating the formula'sfield without also updating theversionvalues creates a stale, misleading formula that looks valid but produces checksum mismatches on install. This has happened before (v0.3.0: version updated to 0.3.0 but SHAs were still from v0.2.13). Always do both or neither.sha256
# Must be on dev branch with clean working tree
git checkout dev
git pull origin dev
git status --porcelain # must be empty
git fetch origin mainWhy this is first: releases have ended up with zero working binaries because a module-init crash or bundler bug only surfaces inoutput, not inbun build --compile. CI catches it — but only AFTER the tag is pushed and a GitHub Release is created. By then the damage (empty release, brokenbun run, brokenreleases/latest) is already live. Failing here, before any user-visible change, keeps the blast radius at "no release was cut."install.sh
# 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/release--bytecodepackage.jsonnode_modules/bun runpyproject.tomlversion = "x.y.z"package.json"version": "x.y.z"Cargo.tomlversion = "x.y.z"go.modpatch0.1.0 -> 0.1.1minor0.1.3 -> 0.2.0major0.3.5 -> 1.0.0# Get all commits on dev that aren't on main
git log main..dev --oneline --no-mergesgit diff main..dev(#12)package.json"version": "x.y.z"pyproject.tomlversion = "x.y.z"Cargo.tomlversion = "x.y.z"scripts/sync-versions.shbash scripts/sync-versions.shpackages/*/package.jsonpackage.jsonbun.lockbun installpackage.jsonpackage-lock.jsonnpm install --package-lock-onlypyproject.tomluv.lockuv lock --quietCargo.tomlcargo update --workspaceCHANGELOG.md## [x.y.z] - YYYY-MM-DD
One-line summary of the release.
### Added
- Entry one (#PR)
- Entry two (#PR)
### Changed
- Entry one (#PR)
### Fixed
- Entry one (#PR)[Unreleased][Unreleased]# 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
)"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 (uses changelog content as release notes)
gh release create vx.y.z --title "vx.y.z" --notes "{changelog section content without the ## header}"
# Sync dev with main so both branches are identical
git checkout dev
git pull origin main
git push origin devImportant: This sync ensures dev has the merge commit from main. Without it, dev and main diverge. The CIjob only pushes the formula commit to dev — it does not bring the PR merge commit onto dev. This manualupdate-homebrewis what ensures dev has the merge commit.git pull origin main
Do NOT useorgit pull origin main --ff-onlyfor this sync. Fast-forward is impossible across a squash merge — main's squash commit has a different SHA than dev's release commit, so dev is never fast-forwardable to main. And resetting dev to main rewrites dev's history, which severs every open PR's merge-base from its original commit and balloons their diffs to thousands of lines (confirmed against v0.3.10's release: PRs went fromgit reset --hard origin/mainto+80/-1after a+6626/-300on dev). The plaingit reset --hard origin/mainabove creates a regular merge commit on dev. The merge bubble in dev'sgit pull origin mainis the right cost for preserving open-PR sanity. If the merge produces agit logconflict during a recovery flow, resolve withhomebrew/archon.rb(note:git checkout origin/main -- homebrew/archon.rb, NOTorigin/main— local main is often stale because the release pushes viamainwithout fast-forwarding the local branch).git push origin dev:main
Note: TheCI job inupdate-homebrewruns automatically after the release job and handles the formula update + push to dev (part of Step 10). Step 11 (tap sync to.github/workflows/release.yml) is always manual. Check the Actions tab before running Step 10 manually.coleam00/homebrew-archon
.github/workflows/release.ymlecho "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
fiTMP_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.rbcat > 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
EOFgit checkout main
git pull origin main
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
git push origin devcoleam00/homebrew-archonbrew tap coleam00/archon && brew install coleam00/archon/archoncoleam00/Archon/homebrew/archon.rbcoleam00/homebrew-archon/Formula/archon.rbTAP_DIR=$(mktemp -d)
git clone git@github.com: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 clonecoleam00/homebrew-archontest-release/test-release brew x.y.zbrew tap coleam00/archon && brew install coleam00/archon/archonBuild: binary/test-release brewx.y.z+1test-release/test-release curl-mac x.y.zinstall.sh/test-release curl-vps x.y.z <vps-target>vx.y.zinstall.shreleases/latestinstall.shreleases/latestgh 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.zgh run list --workflow release.yml --limit 2 --json databaseId,conclusion
gh run view <RUN_ID> --log-failed--bytecodeTypeError: Expected CommonJS module to have a function wrapper--bytecodereadFileSync('package.json')node_modules/process.execPathvx.y.zvx.y.(z+1)vx.y.z/release0.3.8### 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./test-release brewbun build --compilereleases/latestRelease x.y.zhomebrew/archon.rbsha256coleam00/Archon/homebrew/archon.rbcoleam00/homebrew-archon/Formula/archon.rb/test-release brew