name: macos-release
description: Automates the macOS app release pipeline โ Xcode version bump, Release build, DMG packaging, local install, GitHub Release, Homebrew Cask publishing, and GitHub Actions CI/CD. Use for macOS app distribution requests. ํธ๋ฆฌ๊ฑฐ: "๋ฆด๋ฆฌ์ค", "release", "๋ฒ์ ์
๋ฐ์ดํธ", "version bump", "๋ฐฐํฌ", "deploy", "ํ๋ธ๋ฃจ ์
๋ฐ์ดํธ", "brew cask", "DMG ๋ง๋ค๊ธฐ", "์ ๋ฒ์ ๋ฐฐํฌ", "publish", "CI/CD", "workflow", "์๋ ๋ฐฐํฌ", "GitHub Actions", "์ ๋ฒ์ ๋ฐฐํฌํด์ฃผ์ธ์", "๋ฆด๋ฆฌ์ค ์คํฌ๋ฆฝํธ ๋ง๋ค์ด์ฃผ์ธ์", "ํ๋ธ๋ฃจ์ ์ฌ๋ ค์ฃผ์ธ์", "๋ฐฐํฌ ์๋ํ ์ํฌํ๋ก์ฐ ๋ง๋ค์ด์ฃผ์ธ์".
argument-hint: "[release | setup | dry-run | brew-only | ci-setup] [version]"
user: "์ ๋ฒ์ ๋ฆด๋ฆฌ์คํด์ฃผ์ธ์"
assistant: "๊ธฐ์กด ๋ฆด๋ฆฌ์ค ์คํฌ๋ฆฝํธ๋ฅผ ๊ฐ์งํ์ต๋๋ค. ๋จผ์ dry-run์ผ๋ก ํ์ธํ๊ฒ ์ต๋๋ค."
user: "๋ฆด๋ฆฌ์ค ํ์ดํ๋ผ์ธ ๋ง๋ค์ด์ฃผ์ธ์"
assistant: "ํ๋ก์ ํธ ๊ตฌ์กฐ๋ฅผ ๋ถ์ํด์ scripts/release.sh๋ฅผ ์์ฑํ๊ฒ ์ต๋๋ค."
user: "ํ๋ธ๋ฃจ ์บ์คํฌ๋ง ์
๋ฐ์ดํธํด์ฃผ์ธ์"
assistant: "Homebrew tap์ Cask ํ์ผ์ ํ์ฌ ๋ฆด๋ฆฌ์ค์ ๋ง๊ฒ ์
๋ฐ์ดํธํ๊ฒ ์ต๋๋ค."
user: "๋ฐฐํฌ ์๋ํ ์ํฌํ๋ก์ฐ ๋ง๋ค์ด์ฃผ์ธ์"
assistant: "GitHub Actions ์ํฌํ๋ก์ฐ 2๊ฐ(๋น๋+๋ฆด๋ฆฌ์ค, Homebrew ์๋ ์
๋ฐ์ดํธ)๋ฅผ ์์ฑํ๊ฒ ์ต๋๋ค."
macOS App Release Pipeline
Manages the full release lifecycle of a macOS app.
Respond to the user in Korean.
Pipeline Overview
A release has 7 stages. Each stage depends on the success of the previous one:
๋ฒ์ ๋ฒํ โ ๋น๋ โ DMG โ ๋ก์ปฌ ์ค์น โ Git Push โ GitHub Release โ Homebrew Cask
Pipeline design principle: run destructive actions (git push, GitHub release) only after the build and local install succeed. Once local install succeeds the user can verify the app directly, and if there is a problem nothing is public yet, so aborting is easy.
Pre-flight Checks
Required tools
gh CLI installed and authenticated (gh auth status)
xcodebuild available
- Clean git working directory (no uncommitted changes)
- Homebrew tap repository present locally
Project detection
Check the project for:
| Item | Where to look | If missing |
|---|
| Release script | scripts/release.sh | Suggest creating one |
| Xcode project | *.xcodeproj, *.xcworkspace | Specify the path manually |
| Homebrew tap | ../homebrew-tap or ../homebrew-* | Guide creating one |
| Current version | MARKETING_VERSION in pbxproj | Confirm with the user |
Using an Existing Release Script
If scripts/release.sh exists:
- Run a dry-run first:
./scripts/release.sh --dry-run [version]. The dry-run previews every step before anything destructive runs.
- Show the execution plan to the user.
- After confirmation, run:
./scripts/release.sh [version]
Common options:
- No argument: auto-increment the minor version (1.2 โ 1.3)
- Version argument: a specific version (
./scripts/release.sh 2.0)
--skip-brew: skip the Homebrew update
--dry-run: preview all steps
Creating a Release Script
If no script exists, create scripts/release.sh. See references/release-script-guide.md for the detailed guide.
Key design decisions:
Version bump strategy
Read and update MARKETING_VERSION and CURRENT_PROJECT_VERSION in the pbxproj. Change them on disk before building so the built app carries the correct version. Commit after the build succeeds, which makes rollback easy if the build fails.
Local install (quit app โ overwrite โ relaunch)
pkill -x "$APP_NAME" || true
sleep 1
for i in {1..5}; do pgrep -x "$APP_NAME" || break; sleep 1; done
pgrep -x "$APP_NAME" && pkill -9 -x "$APP_NAME"
DMG_MOUNT=$(hdiutil attach "$DMG" -nobrowse -noverify -noautoopen | grep "/Volumes/" | awk '{print $NF}')
rm -rf "$INSTALLED_APP"
ditto "$DMG_MOUNT/$APP.app" "$INSTALLED_APP"
hdiutil detach "$DMG_MOUNT" -quiet
open "$INSTALLED_APP"
Homebrew Cask vs Formula
- Cask: GUI apps (.app) โ distributed as DMG/ZIP
- Formula: CLI tools โ built from source or downloaded as a binary
macOS menu bar apps, status bar apps, and any app with a GUI โ use a Cask.
Homebrew tap management
- Personal unified tap:
homebrew-tap (recommended for multiple projects)
- Per-project tap:
homebrew-{project} (for a single project)
Prefer the unified tap pattern (brew install --cask user/tap/app), since it lets you manage multiple projects from one repo.
Auto-generating release notes
git log --pretty=format:"- %s" "v${PREV_VERSION}..HEAD" | grep -v "Bump version"
Homebrew-only Work
When updating only the cask:
Create a new tap
gh repo create username/homebrew-tap --public
git clone git@github.com:username/homebrew-tap.git
mkdir -p homebrew-tap/Casks
Cask file template
cask "appname" do
version "X.Y"
sha256 "..."
url "https://github.com/USER/REPO/releases/download/v#{version}/App-#{version}.dmg"
name "AppName"
desc "App description"
homepage "https://github.com/USER/REPO"
depends_on macos: ">= :ventura"
app "AppName.app"
zap trash: [
"~/Library/Preferences/com.user.app.plist",
]
end
Resolving push conflicts
Another project may have updated the same tap:
cd "$HOMEBREW_TAP" && git pull --rebase origin main && git push origin main
GitHub Actions Automated Deployment
You can automate deployment with GitHub Actions instead of (or alongside) the local script. See references/github-workflow-guide.md for the detailed guide.
Workflow layout
.github/workflows/
โโโ release.yml โ ๋น๋ + DMG + GitHub Release
โโโ update-homebrew.yml โ Release ์ด๋ฒคํธ ์ Cask ์๋ ์
๋ฐ์ดํธ
Core flow
ํ๊ทธ push (v1.5) โ release.yml (๋น๋+DMG+Release) โ update-homebrew.yml (Cask ์
๋ฐ์ดํธ)
When release.yml creates a GitHub Release, a release.published event fires and update-homebrew.yml is triggered automatically to update the Homebrew Cask.
Prerequisites
- Create a PAT: GitHub Settings โ Fine-grained token โ Contents permission on the homebrew-tap repo
- Register the secret: FrogTray repo Settings โ Secrets โ
HOMEBREW_TAP_TOKEN
update-homebrew.yml core
on:
release:
types: [published]
jobs:
update-cask:
runs-on: ubuntu-latest
steps:
- name: Download DMG and get SHA256
run: |
DMG_URL=$(gh api "repos/$REPO/releases/tags/$TAG" \
--jq '.assets[] | select(.name | endswith(".dmg")) | .browser_download_url')
curl -sL "$DMG_URL" -o app.dmg
SHA256=$(shasum -a 256 app.dmg | awk '{print $1}')
- name: Update Cask and push
Local vs CI guide
| Situation | Recommended |
|---|
| Fast iterative development | Local scripts/release.sh |
| Team project / reproducible builds | GitHub Actions |
| Code signing required | Local (easier keychain access) |
| Automate Homebrew only | Use update-homebrew.yml standalone |
The side-by-side pattern is the most flexible: build and release locally, auto-update Homebrew in CI.
Release Artifact Verification
After DMG creation (Step 3) and before local install (Step 4), verify artifact integrity.
- Verify the built app version:
- Read
CFBundleShortVersionString from .app/Contents/Info.plist
- If it does not match the expected version โ abort the pipeline and report the cause
- Verify the DMG file:
- Check the file size (fail if 0 bytes or abnormally small)
- Compute the SHA256 hash โ include it in the GitHub Release body
- Verify code signing (for signed apps):
- Run
codesign --verify --deep --strict
- On failure โ abort the pipeline
User Re-verification Checkpoint (Step 4 โ Step 5 gate)
After local install (Step 4) succeeds and before the external publish (Step 5: Git Push), insert a user confirmation gate. This checkpoint is the last safety guard before hard-to-undo remote operations.
- Confirm the locally installed app is running
- Confirm with the user via AskUserQuestion:
๋ก์ปฌ ์ค์น๊ฐ ์๋ฃ๋์์ต๋๋ค.
- ์ฑ ๋ฒ์ : {version}
- ์ค์น ๊ฒฝ๋ก: {path}
- DMG: {dmg_path} (SHA256: {hash_prefix}...)
์ฑ์ด ์ ์ ๋์ํ๋์ง ์ง์ ํ์ธํด์ฃผ์ธ์.
๋ค์ ๋จ๊ณ(Git Push + GitHub Release + Homebrew)๋ฅผ ์งํํ ๊น์?
- User approves โ proceed to Step 5 (Git Push)
- User declines โ abort the pipeline, ask for a description of the problem
Failure Recovery and Per-step Restart
Pipeline state tracking
Record each step's success/failure in .claude/release/pipeline-state.json:
{
"version": "1.5",
"startedAt": "2026-03-28T10:00:00Z",
"steps": [
{"step": 1, "name": "version-bump", "status": "completed"},
{"step": 2, "name": "build", "status": "completed"},
{"step": 3, "name": "dmg", "status": "failed", "error": "hdiutil: Resource busy"},
{"step": 4, "name": "local-install", "status": "pending"},
{"step": 5, "name": "git-push", "status": "pending"},
{"step": 6, "name": "github-release", "status": "pending"},
{"step": 7, "name": "homebrew", "status": "pending"}
]
}
Restart logic
- On pipeline run, check whether
pipeline-state.json exists
- If it exists, prompt via AskUserQuestion:
์ด์ ๋ฆด๋ฆฌ์ค v{version}์ด Step {N}({name})์์ ์คํจํ์ต๋๋ค.
์ค๋ฅ: {error}
[1] Step {N}๋ถํฐ ์ฌ์์
[2] ์ฒ์๋ถํฐ ๋ค์ ์์
[3] ์ทจ์
- On restart: re-check only the failed step's prerequisites, then run from that step
- On full completion: delete
pipeline-state.json
Per-step prerequisites
| Restart step | Prerequisite |
|---|
| Step 1 (version bump) | git clean state |
| Step 2 (build) | Version reflected in pbxproj |
| Step 3 (DMG) | Build artifact (.app) exists |
| Step 4 (local install) | DMG file exists |
| Step 5 (Git Push) | Version commit exists + DMG exists |
| Step 6 (GitHub Release) | Tag pushed + DMG exists |
| Step 7 (Homebrew) | GitHub Release exists |
Troubleshooting
| Symptom | Cause | Resolution |
|---|
| Build succeeds but the app does not change | An app from a different build path is running | pkill โ clean build โ launch from the correct path |
| DMG creation fails | Insufficient disk space / a volume with the same name is mounted | Check space, hdiutil detach |
| Homebrew tap push rejected | New commits on the remote | git pull --rebase origin main |
gh release create fails | The same tag already exists | gh release delete vX.Y or use a different version |
| Version does not change in pbxproj | sed pattern mismatch | Check the MARKETING_VERSION = format (mind the whitespace) |
References
Detailed guides live in this skill's references/ directory. Read them with the Read tool only when needed.
references/release-checklist.md: full release checklist, pre-flight checks, dry-run-first principle
references/release-script-guide.md: scripts/release.sh design, dry-run/skip-brew/version-bump/DMG/Cask patterns
references/github-workflow-guide.md: GitHub Actions release + Homebrew auto-update workflows
references/homebrew-publishing.md: Homebrew Cask/Formula choice, shared tap rules, push/rebase patterns
references/local-install-and-dmg.md: DMG/ZIP local install verification, app quit/overwrite/relaunch procedure
references/troubleshooting.md: common failure symptoms during release and recovery directions