Claude Code 스킬로 1인 개발 앱의 iOS 릴리스를 편하게 만든 이야기
목차
React Native + Expo(EAS)로 개발 중인 iOS 앱의 릴리스 작업에서, 개발을 이어가다 보니 자동화할 수 있는 지점이 몇 군데 보이기 시작했습니다. 구체적으로는 **① 릴리스 노트 작성(GitHub)**과 ② iOS 앱 제출(App Store) 두 가지인데, Claude Code의 스킬로 반자동화에 성공했기에 이번에 글로 정리해 봤어요.
구체적으로는 아래 두 스킬입니다.
- 【
/myapp-release-draft <version>】develop→main릴리스 PR 작성, 버전 업데이트, GitHub Release 드래프트 생성까지의 "릴리스 노트 작성 페이즈". - 【
/myapp-deploy】main머지 후 Lint·타입 체크 →eas build --auto-submit으로 프로덕션 빌드 & App Store 제출, 스토어 게재 텍스트 생성까지의 "앱 제출 페이즈".
설계의 축은 **"사람의 승인 포인트를 남긴 채, 정형 작업만 걷어낸다"**는 것입니다. 각 스킬은 SKILL.md라는 Markdown 파일에 절차와 메타데이터를 적는 것만으로 만들고 있어요. 릴리스 플로 전체 흐름은 아래와 같습니다. 빨간색으로 표시했듯 실행 전 적절한 지점마다 사람의 승인(GO 사인)을 끼워 넣고 있습니다.
React Native + Expo에서의 iOS 앱 릴리스 플로
sequenceDiagram
actor Dev as 개발자
box rgb(255,228,225) 【Claude Code】 자동화한 작업
participant Draft as 【릴리스 준비】<br/>/myapp-release-draft
participant Deploy as 【앱 제출】<br/>/myapp-deploy
end
box rgb(225,228,250) 외부 서비스
participant GH as GitHub
participant EAS as EAS /<br/>App Store Connect
end
Note over Dev,EAS: 릴리스 노트 작성 페이즈
Dev->>Draft: /myapp-release-draft A.B.C
Draft->>Draft: 사전 확인 (브랜치·diff)
Draft->>Dev: 현재 → 새 버전 제시
Dev-->>Draft: 🔴 승인
Draft->>Draft: app.json의 버전 /<br/>빌드 번호 업데이트
Draft->>GH: 릴리스 PR 작성
Draft->>Dev: 릴리스 노트 초안 제시
Dev-->>Draft: 🔴 승인
Draft->>GH: 릴리스 노트 드래프트 생성
Draft-->>Dev: PR과 릴리스 노트의<br/>URL 보고
Note over Dev,EAS: 리뷰
Dev->>GH: PR을 리뷰하고 main←develop으로 머지
Note over Dev,EAS: 앱 제출 페이즈
Dev->>Deploy: /myapp-deploy
Deploy->>Deploy: 사전 가드 (브랜치·EAS 로그인)
Deploy->>Deploy: Lint·타입 체크
Deploy->>Dev: 배포 대상 제시
Dev-->>Deploy: 🔴 승인
Deploy->>EAS: eas build --auto-submit<br/>(프로덕션 빌드 & 자동 제출)
Deploy->>GH: 릴리스 노트 가져오기
Deploy-->>Dev: 스토어 게재 텍스트 생성·보고
Dev->>EAS: App Store Connect에서 최종 확인·공개
위 다이어그램은 Mermaid 시퀀스 다이어그램으로 재현한 것입니다. 원문에서 빨간 점선으로 표시된 사람의 승인(GO 사인) 3곳은 🔴 표시로 대체했습니다.
배경
1인 개발로 앱을 계속 업데이트하다 보면 릴리스 때마다 똑같은 작업을 반복하게 됩니다.
app.json의 버전 번호를 올린다develop에서main으로 가는 PR을 매번 같은 포맷으로 만든다- 커밋 로그를
feat:/fix:등으로 분류해 릴리스 노트로 다듬는다 - GitHub Release 드래프트를 만든다
- 머지 후 Lint·타입 체크를 통과시키고 나서 EAS 프로덕션 빌드를 돌린다
- App Store Connect용 프로모션 문구·최신 정보를 쓴다
작업 하나하나는 어렵지 않지만, 절차를 하나 건너뛰면 사고가 나는 유형의 작업입니다. 버전 올리는 걸 깜빡하거나, 빌드 번호가 어긋나거나, 릴리스 노트 형식이 무너지는 등 자잘한 실수가 나기 쉽고, 하필 릴리스 직전이라는 가장 실수하고 싶지 않은 타이밍에 터집니다.
혼자 개발하면 나 말고 리뷰해 줄 사람도 없어서 "내 주의력"만이 품질 게이트가 되어 버리니, 어떻게든 반자동화할 수 없을까 계속 고민하고 있었어요. 그래서 손에 있는 Claude Code로 릴리스 절차 자체를 "실행 가능한 문서"로 스킬화하기로 했습니다. 절차서를 그냥 적는 게 아니라 Claude가 읽고 그대로 실행할 수 있는 형태로 만들면, 절차서 유지보수와 자동화를 동시에 달성할 수 있겠다는 계산이었어요.
어떻게 해결했나
Claude Code의 SKILL.md로 해결했습니다. .claude/skills/<skill-name>/SKILL.md를 만들면 CLI에서 /<skill-name>을 쓸 수 있거든요.
이번 경우로 말하면 .claude/skills/myapp-release-draft/SKILL.md와 .claude/skills/myapp-deploy/SKILL.md를 만들어서 /myapp-release-draft와 /myapp-deploy 두 스킬을 호출할 수 있게 됩니다. SKILL.md에는 메타데이터와 절차를 각각 적습니다.
스킬 ①: /myapp-release-draft — 릴리스 PR 초안을 만든다 (.claude/skills/myapp-release-draft/SKILL.md)
메타데이터
---
name: myapp-release-draft
description: develop→main 릴리스 PR을 작성한다. 인수에 릴리스 버전(예: 3.0.0)을 반드시 지정할 것. app.json의 버전을 지정 버전으로 업데이트해 커밋·푸시하고, PR과 CONTRIBUTING.md 템플릿에 기반한 릴리스 노트 드래프트를 생성한다. 사용 예: /myapp-release-draft 3.0.0
hint: 인수에 릴리스 버전을 반드시 지정하세요(예: /myapp-release-draft 3.0.0). develop 브랜치 위에서 실행하세요.
tools: Bash, Read, Edit
allowed-tools:
- Bash(git branch *)
- Bash(git status *)
- Bash(git fetch *)
- Bash(git log *)
- Bash(git add *)
- Bash(git commit *)
- Bash(git push *)
- Bash(cd *)
- Bash(eas build:list *)
- Bash(gh pr create *)
- Bash(gh release create *)
- Read(src/MyApp/app.json)
- Edit(src/MyApp/app.json)
---
당신은 릴리스 엔지니어로서 아래 절차를 순서대로 실행한다. 도중에 에러가 발생하면 처리를 멈추고 사용자에게 보고하라.
> **전제**: 이 스킬은 리포지토리 루트(`.claude/`나 `CLAUDE.md`가 있는 계층)를 작업 디렉터리로 실행하는 것을 전제로 한다. 이 문서의 경로는 모두 리포지토리 루트 기준 상대 경로다.
## 인수
`$ARGUMENTS`에 릴리스 버전 문자열(예: `3.0.0`)이 전달된다.
인수가 비어 있으면 "버전을 인수로 지정해 주세요(예: /myapp-releases-draft 3.0.0)"라고 전하고 처리를 멈춘다.
## 상수
- **새 버전**: `$ARGUMENTS` <!-- $ARGUMENTS에 인수의 버전 정보가 담깁니다 -->
- **앱 리포지토리**: 리포지토리 루트(이 스킬을 실행 중인 작업 디렉터리)
- **app.json 경로**: `src/MyApp/app.json`
- **GitHub 리포지토리**: `...`
---
## 절차
...
description과 hint는 스킬을 언제·어떻게 호출할지를 Claude 자신에게 알려 주기 위한 서술입니다. 그리고 allowed-tools는 이 스킬이 실행해도 되는 툴/커맨드의 화이트리스트예요. 위 설정이라면 다음 4가지 조작이 허용돼요.
- Git 조작 (
git) - GitHub 조작 (
gh *) - EAS 조작 (
eas build:list) app.json읽기·편집
여기에 없는 조작, 예를 들어 rm 같은 파괴적 커맨드나 무관한 파일에 대한 쓰기는 구조적으로 튕겨 냅니다. 자동화와 안전성을 양립시키는 토대가 돼요.
[검증] 위 문단은 원문의 설명을 그대로 옮긴 것이지만, 현행 공식 문서 기준으로 allowed-tools는 나열된 툴을 "확인 프롬프트 없이 사전 승인"하는 목록이며, 목록에 없는 툴을 차단하는 기능은 아닙니다. 나열되지 않은 조작은 평소처럼 사용자 확인을 거치게 될 뿐 실행 자체가 구조적으로 막히는 것은 아니므로, 특정 툴을 확실히 차단하려면 스킬 프론트매터의 disallowed-tools나 권한 설정(permissions)의 deny 규칙을 함께 쓰는 것이 안전합니다. 다만 이 동작은 버전에 따라 달라질 수 있으니 적용 전에 최신 공식 문서를 확인하세요.
위 메타데이터 설명 외에, 본문 첫 문장, 스킬 안에서 쓰는 인수나 상수 같은 정보를 파일 서두에 적어 두면 스킬에서 사용할 데이터를 Claude가 인식하게 됩니다.
절차
develop 브랜치 위에서 /myapp-release-draft 3.0.0처럼 버전을 인수($ARGUMENTS)로 넘겨 실행합니다. SKILL.md에는 서두의 메타데이터·상수·인수에 더해 다음 7단계가 절차로 적혀 있어요.
1. 사전 확인. 먼저 브랜치와 작업 트리 상태를 체크합니다.
git branch --show-current
git status --porcelain
- 현재 브랜치가
develop이 아니면 멈추고 "develop브랜치로 전환한 뒤 실행해 주세요"라고 전한다 - uncommitted 변경이 있으면 멈추고, 변경 내용을 나열해 사용자에게 확인을 요청한다
"잘못된 브랜치에서 릴리스를 만드는" 사고를 처음부터 막습니다.
2. 버전 확인. app.json의 expo.version을 읽고 다음 형태로 사용자에게 승인을 요청합니다. 승인을 받은 뒤에야 다음으로 진행해요.
현재 버전: <app.json의 expo.version>
새 버전: $ARGUMENTS
버전을 이 내용으로 업데이트해도 되겠습니까?
3. 커밋 목록 수집과 분류. main과의 diff 커밋을 가져와 접두사로 3그룹으로 분류합니다.
git fetch origin
git log origin/main..develop --oneline
- 기능 개선 리스트:
feat:으로 시작하는 커밋 - 버그 수정 리스트:
fix:로 시작하는 커밋 - 개선 리스트:
refactor:/chore:로 시작하는 커밋 - 비고 리스트: 그 외로 시작하는 커밋
이때 커밋 메시지의 접두사(feat: 등)는 제거하고, PR 번호(#NNN)는 끝에 남깁니다. 다음 단계의 릴리스 노트 정리에 그대로 재사용할 수 있어요.
4. app.json 업데이트 (버전 + 빌드 번호). 먼저 EAS에서 최근 production 빌드의 빌드 번호를 가져옵니다.
cd src/MyApp && eas build:list --platform ios --profile production --status finished --limit 1 --json --non-interactive
JSON 출력의 appBuildVersion 필드가 빌드 번호(CFBundleVersion)입니다. 가져오지 못하면 사용자에게 현재 빌드 번호를 물어봐요. 이어서 Edit 툴로 app.json의 두 필드를 동시에 고쳐 씁니다.
| 필드 | 업데이트 값 |
|---|---|
expo.version | $ARGUMENTS |
expo.ios.buildNumber | 가져온 빌드 번호 (문자열) |
ios.buildNumber는 위젯 버전으로 쓰이기 때문에 EAS에 실제 기록된 값과 동기화해 둘 필요가 있습니다. 손으로 입력하지 않고 EAS 값을 정답으로 삼아 가져오면 심사나 빌드에서 막히는 사고를 막을 수 있어요. 업데이트가 끝나면 커밋 & 푸시.
git add src/MyApp/app.json
git commit -m "chore: 버전을 Ver$ARGUMENTS로 업데이트"
git push origin develop
5. 릴리스 PR 작성. 아래 PR 포맷에 따라 본문 템플릿을 3단계의 분류 결과로 채워 gh pr create를 실행합니다.
gh pr create \
--repo your-org/your-app \
--base main \
--head develop \
--title "release: Ver$ARGUMENTS" \
--body "$(cat <<'EOF'
## 변경 내용
<3단계에서 분류한 커밋 목록을 불릿으로 기재 (feat/fix/refactor 등 포함)>
## 변경 이유
- Ver$ARGUMENTS 릴리스
## 동작 확인 방법
- develop 브랜치에서 동작 확인 완료
## 리뷰 관점
- 릴리스 내용에 누락·오류가 없을 것
- 버전이 올바르게 업데이트되었을 것 (app.json: Ver$ARGUMENTS)
## 우려 사항
-
## 기타
-
EOF
)"
6. 릴리스 노트 정리 + GitHub Release 드래프트 작성. 3단계의 분류 결과를 바탕으로 아래 포맷에 따라 정리합니다.
# 변경 목록
- **릴리스일**: YYYY년 MM월 DD일
- [App Store 링크](<App Store의 URL>)
## 👍기능 개선
### <기능 개선 리스트의 각 항목> #<PR번호>
## 🐛버그 수정
### <버그 수정 리스트의 각 항목> #<PR번호>
## 🔄개선
### <개선 리스트의 각 항목> #<PR번호>
## ⚠️주의
-
## ✍비고
- iOS 앱
- 해당 커밋이 없는 섹션은 생략한다
## ✍비고에는 비고 리스트의 커밋을 기재하고,- iOS 앱은 끝에 남긴다
정리한 릴리스 노트를 일단 사용자에게 보여 주고 수정할 곳이 있는지 확인한 뒤, --draft로 GitHub Release를 만듭니다(공개는 하지 않습니다).
gh release create "Ver$ARGUMENTS" \
--repo your-org/your-app \
--title "Ver$ARGUMENTS" \
--notes "<확인 완료된 릴리스 노트 내용>" \
--draft
태그는 Ver$ARGUMENTS(예: Ver3.0.0). 생성 후에는 Release의 URL을 기록합니다.
7. 완료 보고. 마지막으로 다음 형태로 정리해 보고하고 끝냅니다.
✅ 릴리스 PR과 GitHub Release 드래프트를 작성했습니다
버전: <구 버전> → $ARGUMENTS
PR URL: <작성된 PR의 URL>
Release URL: <작성된 GitHub Release의 URL>
--- 릴리스 노트 드래프트 ---
<6단계에서 확인 완료된 릴리스 노트 내용>
여기까지가 초안 페이즈이고, 실제 main 머지는 리뷰를 거쳐 사람이 합니다.
스킬 ②: /myapp-deploy — 머지 후 프로덕션 빌드 & 제출 (.claude/skills/myapp-deploy/SKILL.md)
릴리스 PR을 main에 머지했으면 main 브랜치 위에서 /myapp-deploy를 실행합니다. 프론트매터는 다음과 같고, allowed-tools는 인수까지 거의 고정된 형태로 화이트리스트화했어요.
메타데이터
---
name: myapp-deploy
description: main 브랜치에 릴리스 PR을 머지한 뒤 실행하는 배포 스킬. Lint·타입 체크 사전 확인을 하고, eas build --auto-submit으로 iOS 프로덕션 빌드 & App Store 제출을 일괄 실행해 결과를 보고한다. 사용 예: /myapp-deploy
hint: main 브랜치 위에서 실행하세요. EAS CLI에 로그인되어 있는지 확인하세요(eas whoami).
tools: Bash, Read
allowed-tools:
- Read(src/MyApp/app.json)
- Bash(git branch *)
- Bash(git status *)
- Bash(git pull *)
- Bash(cd *)
- Bash(eas whoami)
- Bash(npm run lint)
- Bash(npx tsc --noEmit)
- Bash(eas build --platform ios --profile production --auto-submit --non-interactive)
- Bash(gh release view *)
---
당신은 릴리스 엔지니어로서 아래 절차를 순서대로 실행한다. 도중에 에러가 발생하면 처리를 멈추고 사용자에게 보고하라.
> **전제**: 이 스킬은 리포지토리 루트(`.claude/`나 `CLAUDE.md`가 있는 계층)를 작업 디렉터리로 실행하는 것을 전제로 한다. 이 문서의 경로는 모두 리포지토리 루트 기준 상대 경로다.
## 상수
- **앱 리포지토리**: 리포지토리 루트(이 스킬을 실행 중인 작업 디렉터리)
- **작업 디렉터리**: `src/MyApp`
- **app.json 경로**: `src/MyApp/app.json`
본문은 마찬가지로 "당신은 릴리스 엔지니어로서…"로 시작하고, 상수로 앱 리포지토리·작업 디렉터리(src/MyApp)·app.json 경로를 고정한 다음, 아래 절차를 순서대로 실행합니다.
절차
1. 사전 확인. 브랜치와 작업 트리 상태를 확인합니다.
git branch --show-current
git status --porcelain
- 현재 브랜치가
main이 아니면 멈추고 "main브랜치로 전환한 뒤 실행해 주세요"라고 전한다 - uncommitted 변경이 있으면 멈추고, 변경 내용을 나열해 확인을 요청한다
2. 최신 코드 동기화.
git pull origin main
3. EAS 로그인 확인. 실패하면 멈추고 "eas login으로 로그인한 뒤 실행해 주세요"라고 전합니다.
cd src/MyApp && eas whoami
4. 배포 대상 버전 확인. app.json의 expo.version을 읽고 다음 형태로 승인을 요청합니다.
배포 대상 버전: <app.json의 expo.version>
아래 내용으로 배포를 시작해도 되겠습니까?
- platform: iOS
- profile: production
- auto-submit: 유효 (빌드 완료 후 App Store Connect에 자동 제출)
5. Lint 체크 & 타입 체크. 빌드를 돌리기 전에 반드시 통과시키고, 에러가 나면 그 시점에 멈춰서 내용을 보고합니다. CI가 가벼운 1인 개발이라도 최소한의 게이트를 릴리스 직전에 반드시 거치는 운영이 가능해져요.
cd src/MyApp && npm run lint && npx tsc --noEmit
6. EAS 빌드 & 자동 제출. 게이트 통과 후 프로덕션 빌드. --auto-submit으로 완료 후 App Store Connect에 자동 제출, --non-interactive로 대화 없이 실행합니다.
cd src/MyApp && eas build --platform ios --profile production --auto-submit --non-interactive
빌드는 EAS 서버에서 비동기로 돌아가므로, 완료를 기다리지 않고 EAS 대시보드 URL을 제시하고 다음으로 진행합니다.
EAS 공식 문서 기준으로 --auto-submit이 자동화하는 범위는 "빌드 완료 후 App Store Connect(TestFlight) 업로드"까지입니다. TestFlight에 올라간 빌드를 App Store 심사에 제출하는 마지막 단계는 App Store Connect에서 수동으로 진행해야 하니, 전체 흐름을 설계할 때 참고하세요.
7. 완료 보고. 다음 형태로 빌드가 시작됐음을 보고합니다.
✅ EAS 빌드 & 서브밋을 시작했습니다
버전: <app.json의 expo.version>
플랫폼: iOS
프로파일: production
auto-submit: 유효
빌드 진행 상황은 EAS 대시보드에서 확인해 주세요:
<EAS 대시보드 URL>
빌드 완료 후 자동으로 App Store Connect에 제출됩니다.
8. App Store Connect 텍스트 생성. 마지막으로 최신 릴리스의 릴리스 노트를 가져옵니다.
gh release view --json tagName,body
가져온 릴리스 노트(👍기능 개선 / 🐛버그 수정 / 🔄개선 섹션)를 바탕으로, App Store Connect에 붙여 넣을 두 가지 텍스트를 한국어로 생성합니다. SKILL.md에는 생성 방침이 그대로 지시로 적혀 있어요.
프로모션용 텍스트(Promotional Text)는 App Store 앱 페이지 상단에 표시되는 마케팅 문구로 170자 이내(초과하면 줄임). 이번 릴리스에서 추가·개선된 주요 기능을 사용자 눈높이에서 "무엇이 좋아졌는지"를 간결하게 전달합니다. 최신 정보(What's New)는 업데이트 화면에 표시되는 릴리스 노트로, 기능 개선·버그 수정·개선을 불릿으로 정리하고, 전문 용어를 피하고, 맨 앞에 "Ver<버전 번호> 업데이트 내용" 제목을 붙입니다.
출력 포맷은 다음과 같습니다.
---
📣 프로모션용 텍스트 (170자 이내)
<생성한 프로모션용 텍스트>
글자 수: <글자 수>/170
---
📝 최신 정보 (What's New)
Ver? 업데이트를 전해 드립니다!
■ 내용 1
【상세】
■ 내용 2
【상세】
...
---
공들인 부분
이상이 SKILL.md의 내용입니다. 개발자 눈높이의 릴리스 노트를 그대로 일반 사용자용 스토어 게재문으로 번역·요약해 주는 처리가 개인적으로는 제일 반가운 부분이에요. 게재문 생성에서 공들인 장치로는 템플릿 준비와 글자 수 제약 설정이 있습니다. 덕분에 톤이 흔들리지 않고 안정적으로 문장이 나오고 있어요.
설계 시 포인트는 세 가지입니다.
- 【첫째: 승인 포인트를 의도적으로 남긴 것】 전자동으로 만들지 않고 "버전 업데이트 전", "릴리스 노트 확정 전", "빌드 시작 전" 세 군데에 사람의 승인(의사결정)을 끼워 넣었다. 자동화하고 싶은 건 "절차"이지 "의사결정"이 아니므로, 이 구분이 좋았다고 생각한다.
- 【둘째:
allowed-tools를 최소 권한으로 한 것】 deploy 쪽은 인수까지 포함해 거의 고정된 형태로 화이트리스트화했다. 와일드카드를 함부로 넓히지 않아, 스킬이 예상 밖의 조작을 하는 것을 "구조적으로" 막을 수 있다. - 【셋째: 2페이즈로 나눈 것】 사이에 사람의 리뷰와 머지라는 비자동 공정이 반드시 들어가므로, 페이즈 경계를 사람의 판단 경계와 일치시킨 것이 결과적으로 정답이었다.
감상
전체적으로 릴리스 절차서를 "실행 가능한 문서"로 바꿀 수 있었던 것이 제일 큰 수확입니다. 절차서는 손질되지 않고 낡아 가기 쉽지만, 스킬은 실제로 매번 실행되니 항상 최신이고 올바른 상태로 유지돼요. allowed-tools에 의한 화이트리스트, 승인 포인트 설계, 페이즈 분할 — 이 부분을 의식하면 다른 프로젝트에도 응용하기 쉬울 거예요.
실제로 운영해 보면서 느낀 점은 이렇습니다.
- 절차 누락, 특히 버전·빌드 번호 관련 사고가 사라졌다
- PR·릴리스 노트·스토어 게재문의 형식이 매번 갖춰진다
- 릴리스 때마다 있던 "어… 다음이 뭐였더라" 하는 자잘한 회상 비용이 0이 됐다
그리고 구조 설계 관점에서 "금지 사항"도 중요하다는 걸 실감했습니다. "똑똑하니까 괜찮다"가 아니라 "애초에 못 하게 만든다"는 발상이 자동화를 신뢰하는 데 중요하다는 생각이 들었어요.
이 글이 도움이 됐다면 추천해 주세요