🌊

Androidアプリの全自動リリース作業をする

に公開

この記事は2025 ZAICO アドベントカレンダーの12日目の記事です。

はじめに

Androidアプリのリリース作業、手順は決まっているのに毎回確認しながら進めていませんか?

「マイルストーンのIssueを確認して、リリースPRを作って、リリースノートを書いて、内部テストに配布して、確認が取れたら本番にプロモートして...」

こういった一連の流れを 「リリースして」の一言で実行できたら 便利ですよね。

弊社のアプリでもリリースは週に1回、多い時で2回する事もあるのですが、一部まだ手動で作業している所もあり、出来るだけリリース作業をする人にも負担をかけずにやりたい、出来る事ならほとんどの作業を自動化したいと思いから自動化を考えていました。

その考えていた点を解決するため、本記事ではClaude Code を使ってGitHub Actions と Fastlane を組み合わせ、自然言語でリリース作業を行う仕組みを構築してみたいと思います。

自動化の完成イメージ

作業者はClaudeに対してリリース作業を依頼するだけで後は対話していきながら自動でリリース作業を進めていける形となります。

作業者: 今週のリリースをお願い

Claude Code: 📋 マイルストーン「v2.2.0」を確認します...
            完了したIssue: 8件
            
            📝 リリースノートを生成中...
            ─────────────────────
            【新機能】
            ・在庫データをCSV形式でエクスポートできるようになりました
            
            【改善】
            ・バーコードスキャンの安定性を向上しました
            ─────────────────────
            
            🚀 GitHub Actions をトリガーします...
            ✅ リリースPR作成完了: #234
            ✅ 内部テストへの配布開始
            
            📣 Slackに通知しました
            「🚀 v2.2.0 の内部テスト配信を開始しました」

前提条件

  • GitHub Actions でのCI/CD環境が構築済み
  • Fastlane で Google Play Console への配布設定済み
  • Claude Code CLI がインストール済み

全体の流れ

対話をしながら実行していくワークフローは下記の通りとなります。

┌─────────────────────────────────────────────────────┐
│              Claude Code
│  自然言語で指示 → 適切なワークフローを実行            │
└─────────────────────────────────────────────────────┘
        │
        ├─── 1. マイルストーン確認 & リリースノート生成
        │       └→ GitHub API でIssue/PR を取得し、AIが要約
        │
        ├─── 2. GitHub Actions トリガー
        │       └→ gh workflow run でリリースワークフローを実行
        │
        ├─── 3. 内部テスト配布
        │       └→ Fastlane supply で内部テストトラックに配布
        │
        └─── 4. 本番プロモート
                └→ Fastlane supply で本番トラックに昇格

実装

Step 1: プロジェクトに CLAUDE.md を作成

Claude Code がプロジェクトのコンテキストを理解するために、CLAUDE.md をリポジトリルートに配置します。
このファイルに詳細に情報を書いていく事で、より精度の高いかつ曖昧な自然言語でも汎用性のある形でリリース作業を進める事ができます。

CLAUDE.md
# プロジェクト概要

Android〇〇アプリのリポジトリです。

## リリースフロー

1. マイルストーンに紐づくIssue/PRがすべてクローズされていることを確認
2. GitHub Actions でリリースPRを作成
3. リリースPRにリリースノートを記載
4. マージ後、GitHub Actions で内部テストに配布
5. QA確認後、本番にプロモート

## バージョン管理

- `app/build.gradle.kts` で versionCode / versionName を管理
- マイルストーン名がバージョン名と対応(例: v2.2.0)

## GitHub Actions ワークフロー

### リリースPR作成 & 内部テスト配布
gh workflow run release-internal.yml -f version=<version>

### 本番プロモート
gh workflow run promote-production.yml -f version=<version>

## Slack通知
リリース通知は #dev-releases チャンネルに送信してください。
Webhook URL: $SLACK_WEBHOOK_URL 環境変数を使用

## 注意事項
- 本番プロモートは必ず確認を求めること
- リリースノートは日本語で作成

Step 2: GitHub Actions ワークフローの作成

リリースPR作成 & 内部テスト配布ワークフロー

このワークフローは workflow_dispatch で手動トリガーされ、以下を自動化します:

  1. リリースブランチの作成: release/v{version} ブランチを作成
  2. バージョン更新: build.gradle.ktsversionCodeversionName を更新
  3. リリースノート生成: マイルストーンに紐づくIssue/PRから自動生成(ラベルで分類)
  4. リリースPR作成: 生成したリリースノートを本文としてPRを作成
  5. ビルド & 配布: AAB をビルドし、内部テストトラックに配布

Claude Code からは gh workflow run release-internal.yml -f version=2.2.0 で実行できます。

.github/workflows/release-internal.yml
name: Release Internal Test

on:
  workflow_dispatch:
    inputs:
      version:
        description: 'リリースバージョン (例: 2.2.0)'
        required: true
        type: string

jobs:
  create-release-pr:
    runs-on: ubuntu-latest
    outputs:
      pr_number: ${{ steps.create-pr.outputs.pr_number }}
      release_notes: ${{ steps.generate-notes.outputs.notes }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Git
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"

      - name: Create release branch
        run: |
          git checkout -b release/v${{ inputs.version }}

      - name: Update version
        run: |
          # versionName を更新
          sed -i "s/versionName = \".*\"/versionName = \"${{ inputs.version }}\"/" app/build.gradle.kts
          
          # versionCode をインクリメント
          CURRENT_CODE=$(grep -oP 'versionCode = \K\d+' app/build.gradle.kts)
          NEW_CODE=$((CURRENT_CODE + 1))
          sed -i "s/versionCode = $CURRENT_CODE/versionCode = $NEW_CODE/" app/build.gradle.kts
          
          git add app/build.gradle.kts
          git commit -m "Bump version to ${{ inputs.version }}"

      - name: Generate release notes from milestone
        id: generate-notes
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          # マイルストーンからクローズされたIssue/PRを取得
          MILESTONE="v${{ inputs.version }}"
          
          echo "## 🎉 v${{ inputs.version }} リリースノート" > release_notes.md
          echo "" >> release_notes.md
          
          # 機能追加 (enhancement ラベル)
          echo "### ✨ 新機能" >> release_notes.md
          gh issue list --milestone "$MILESTONE" --state closed --label "enhancement" --json title,number \
            --jq '.[] | "- \(.title) (#\(.number))"' >> release_notes.md
          echo "" >> release_notes.md
          
          # バグ修正 (bug ラベル)
          echo "### 🐛 バグ修正" >> release_notes.md
          gh issue list --milestone "$MILESTONE" --state closed --label "bug" --json title,number \
            --jq '.[] | "- \(.title) (#\(.number))"' >> release_notes.md
          echo "" >> release_notes.md
          
          # その他
          echo "### 🔧 その他の改善" >> release_notes.md
          gh issue list --milestone "$MILESTONE" --state closed --json title,number,labels \
            --jq '.[] | select(.labels | map(.name) | index("enhancement") == null and index("bug") == null) | "- \(.title) (#\(.number))"' >> release_notes.md
          
          # 出力
          NOTES=$(cat release_notes.md)
          echo "notes<<EOF" >> $GITHUB_OUTPUT
          echo "$NOTES" >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT

      - name: Push release branch
        run: |
          git push origin release/v${{ inputs.version }}

      - name: Create Pull Request
        id: create-pr
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          PR_URL=$(gh pr create \
            --title "🚀 Release v${{ inputs.version }}" \
            --body-file release_notes.md \
            --base main \
            --head release/v${{ inputs.version }})
          
          PR_NUMBER=$(echo $PR_URL | grep -oP '\d+$')
          echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
          echo "✅ Created PR: $PR_URL"

  build-and-deploy-internal:
    needs: create-release-pr
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: release/v${{ inputs.version }}

      - name: Set up JDK
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Setup Ruby for Fastlane
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.2'
          bundler-cache: true

      - name: Decode Keystore
        run: |
          echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > app/release.keystore

      - name: Build Release APK/AAB
        run: |
          ./gradlew bundleRelease
        env:
          KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
          KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
          KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}

      - name: Deploy to Internal Test Track
        run: |
          bundle exec fastlane android internal
        env:
          SUPPLY_JSON_KEY_DATA: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }}

      - name: Comment on PR
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh pr comment ${{ needs.create-release-pr.outputs.pr_number }} \
            --body "✅ 内部テストトラックへの配布が完了しました!

          📱 **テスト方法**
          1. [Google Play Console](https://play.google.com/console/) で内部テストを確認
          2. 内部テスターグループでインストール
          
          🔍 **確認事項**
          - [ ] 新機能の動作確認
          - [ ] 既存機能のリグレッションテスト
          - [ ] クラッシュが発生していないこと
          
          確認完了後、このPRをマージして本番プロモートに進んでください。"

本番プロモートワークフロー

内部テストで検証済みのビルドを本番トラックに昇格させるワークフローです。

ポイントは段階的ロールアウトに対応していること。いきなり100%に配布するのではなく、まず10%のユーザーに配布し、問題がなければ徐々に拡大できます。Play Console から手動でロールアウト率を変更することも可能です。

Claude Code からは gh workflow run promote-production.yml -f version=2.2.0 -f rollout_percentage=10 で実行します。

.github/workflows/promote-production.yml
name: Promote to Production

on:
  workflow_dispatch:
    inputs:
      version:
        description: 'プロモートするバージョン (例: 2.2.0)'
        required: true
        type: string
      rollout_percentage:
        description: 'ロールアウト率 (1-100)'
        required: true
        default: '10'
        type: string

jobs:
  promote:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Ruby for Fastlane
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.2'
          bundler-cache: true

      - name: Promote to Production
        run: |
          bundle exec fastlane android promote_to_production rollout:${{ inputs.rollout_percentage }}
        env:
          SUPPLY_JSON_KEY_DATA: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }}

      - name: Create GitHub Release
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh release create "v${{ inputs.version }}" \
            --title "v${{ inputs.version }}" \
            --generate-notes

Step 3: Fastlane の設定

Fastlane の supply アクションを使って Google Play Console への配布を自動化します。

事前準備として以下が必要です:

  1. Google Play Console で API アクセスを有効化
  2. サービスアカウントを作成し、JSON キーを取得
  3. SUPPLY_JSON_KEY_DATA 環境変数に JSON キーを設定

2つの lane を定義しています:

  • internal: AAB を内部テストトラックにアップロード。内部テストは審査なしで最大100人のテスターに即座に配布できます
  • promote_to_production: 内部テストから本番トラックへビルドを昇格。再アップロード不要でトラック間移動のみ行います
fastlane/Fastfile
default_platform(:android)

platform :android do
  desc "内部テストトラックにデプロイ"
  lane :internal do
    supply(
      track: 'internal',
      aab: 'app/build/outputs/bundle/release/app-release.aab',
      skip_upload_metadata: true,
      skip_upload_images: true,
      skip_upload_screenshots: true
    )
  end

  desc "内部テストから本番にプロモート"
  lane :promote_to_production do |options|
    rollout = (options[:rollout] || 10).to_f / 100.0
    
    supply(
      track: 'internal',
      track_promote_to: 'production',
      rollout: rollout.to_s,
      skip_upload_metadata: true,
      skip_upload_images: true,
      skip_upload_screenshots: true
    )
  end
end

Step 4: Claude Code 用のカスタムコマンド作成

Claude Code では .claude/commands/ ディレクトリにマークダウンファイルを配置することで、カスタムコマンドを定義できます。/release と入力するだけで、定義した手順を Claude Code が実行してくれます。

ここでは2つのコマンドを作成します:

  • /release: マイルストーン確認 → リリースPR作成 → 内部テスト配布 → Slack通知
  • /promote: 最終確認 → 本番プロモート → Slack通知

カスタムコマンドには「何をどの順番で実行するか」を自然言語で記述します。Claude Code はこれを読み取って、適切なコマンドを実行してくれます。

リリース開始コマンド

.claude/commands/release.md
# release

リリースプロセスを開始します。

## 実行手順

1. まず、対象のマイルストーンを確認してください
   - `gh api repos/{owner}/{repo}/milestones --jq '.[] | select(.state=="open") | "\(.title): \(.open_issues)件未完了 / \(.closed_issues)件完了"'`

2. マイルストーンの未完了Issueがある場合は、ユーザーに確認を求めてください

3. 以下のコマンドでリリースワークフローをトリガーします
   - `gh workflow run release-internal.yml -f version=<version>`
   - version はマイルストーン名から `v` を除いた形式 (例: 2.2.0)

4. ワークフローの実行状況を確認します
   - `gh run list --workflow=release-internal.yml --limit 1`

5. 完了したら、Slack に通知します(Webhook URLは環境変数 SLACK_WEBHOOK_URL を使用)

6. 最後に、作成されたPRのURLをユーザーに伝えてください

## 使用例

ユーザー: 今週のリリースをお願い
→ マイルストーン一覧を表示し、対象を確認後、リリースプロセスを実行

本番プロモートコマンド

.claude/commands/promote.md
# promote

内部テストアプリを本番環境にプロモートします。

## 実行前の確認事項

1. 対象のリリースPRを確認
   - `gh pr list --state merged --limit 5`

2. 内部テストの結果を確認(ユーザーに口頭で確認)
   - QAチームからの承認は得られていますか?
   - Crashlyticsで新規クラッシュは報告されていませんか?

3. **必ずユーザーに最終確認を求めてください**
   - 「本番環境へのプロモートを実行してよろしいですか?」

## 実行手順

1. 確認が取れたら、プロモートワークフローをトリガー
   - `gh workflow run promote-production.yml -f version=<version> -f rollout_percentage=<percentage>`
   - デフォルトのロールアウト率は 10%

2. ワークフローの実行状況を確認
   - `gh run list --workflow=promote-production.yml --limit 1`

3. 完了したら、Slack に通知します
   - 「🎉 v<version> を本番環境にリリースしました(ロールアウト: <percentage>%)」

## 使用例

ユーザー: 内部テストOKだったので本番に出して
→ 最終確認後、プロモートを実行

Slack通知用ヘルパースクリプト

scripts/notify-slack.sh
#!/bin/bash

MESSAGE=$1
WEBHOOK_URL=${SLACK_WEBHOOK_URL}

if [ -z "$WEBHOOK_URL" ]; then
  echo "Error: SLACK_WEBHOOK_URL is not set"
  exit 1
fi

curl -X POST -H 'Content-type: application/json' \
  --data "{\"text\":\"${MESSAGE}\"}" \
  "$WEBHOOK_URL"

Step 5: 実際に使ってみる

内部テスト配布

$ claude

作業者: 今週のリリースをお願い

Claude Code: 📋 オープン中のマイルストーンを確認します...

            v2.2.0: 0件未完了 / 8件完了 ✅
            v2.3.0: 5件未完了 / 2件完了

            v2.2.0 のすべてのIssueが完了しています。
            このバージョンをリリースしますか?

作業者: はい、お願いします

Claude Code: 🚀 リリースワークフローを実行します...
            
            $ gh workflow run release-internal.yml -f version=2.2.0
            ✅ ワークフローがトリガーされました
            
            ⏳ 実行状況を確認中...
            Run #1234: in_progress
            
            ... (数分後) ...
            
            ✅ ワークフロー完了!
            
            📝 作成されたPR: https://github.com/your-org/repo-android/pull/234
            📱 内部テストトラックへの配布が完了しました
            
            📣 Slackに通知しました:
            「🚀 v2.2.0 の内部テスト配信を開始しました」
            
            QAチームに確認を依頼してください。
            確認が完了したら `/promote` で本番リリースできます。

本番プロモート

作業者: QA確認取れたので本番に出して

Claude Code: 📋 確認事項をチェックします...
            
            最新のマージ済みリリースPR:
            - #234: 🚀 Release v2.2.0 (2日前にマージ)
            
            ⚠️ 本番環境へのプロモートを実行します。
            
            以下の内容で間違いないですか?
            - バージョン: v2.2.0
            - ロールアウト率: 10%(デフォルト)
            
            実行してよろしいですか? (yes/no)

作業者: yes

Claude Code: 🚀 プロモートワークフローを実行します...
            
            $ gh workflow run promote-production.yml -f version=2.2.0 -f rollout_percentage=10
            ✅ ワークフローがトリガーされました
            
            ⏳ 実行状況を確認中...
            
            ... (数分後) ...
            
            ✅ 本番プロモート完了!
            
            📣 Slackに通知しました:
            「🎉 v2.2.0 を本番環境にリリースしました(ロールアウト: 10%)」
            
            Play Console でロールアウト状況を監視してください。
            問題なければ、段階的にロールアウト率を上げていきましょう。

AIならではの付加価値

この仕組みの真価は、単なるコマンド実行の代行ではありません。

1. コンテキストを理解した判断

作業者: リリースして

Claude Code: ⚠️ マイルストーン v2.2.0 にはまだ未完了のIssueが2件あります:
            - #230: 在庫インポート時のバリデーション強化 (P1)
            - #232: ダークモード対応 (P2)
            
            P1のIssueが含まれていますが、このままリリースしますか?
            それとも次週に延期しますか?

2. リリースノートの自動生成・要約

マイルストーンのIssue/PRから、ユーザー向けの分かりやすいリリースノートを自動生成します。技術的な変更をエンジニア以外のメンバーにもわかりやすい文章に変換できます。

3. 異常検知と提案

作業者: 本番に出して

Claude Code: ⚠️ 内部テスト配布から24時間経過していません(現在: 18時間)
            通常は24時間以上のテスト期間を設けています。
            
            - このまま続行する
            - 明日まで待つ(リマインドしますか?)
            
            どちらにしますか?

最後に

Claude Codeと組み合わせてを使うことで、CI/CD パイプラインを実行しながら、自然言語でリリース作業を行えるようになりました。

また自然言語での対話ができる事で、マイルストーンの選択や途中で失敗やエラーになった際にも柔軟に対応が出来るのがAIの強みに思いました。

人間は「いいね、進めて」と言うだけ。これが AI 時代のリリースフローです。

参考リンク


次回はtakakさんのAPIドキュメントに関する興味深い話しです!お楽しみに!

ZAICO Developers Blog

Discussion