この記事はコインチェック株式会社(以下、コインチェック)のアドベントカレンダー3日目の記事です。
qiita.com
自己紹介
みなさん、こんにちはこんばんは。
コインチェックのプロダクトエンジニアリング部のリスティング&プロトコル GでエンジニアをしているYotaです。
普段はステーキング関連の開発を主にしています。
たまに勉強会に登壇しています。 coincheck.connpass.com
今回は、チケット作成からプルリクエスト作成までを自動化するツールを作成してみたので紹介します。
はじめに
バックログ、気づいたら増えていませんか?
ある課題を見つけてチケット起票をしても、結局開発リソースの関係などで手付かずのチケットがバックログに取り残されてませんでしょうか。
チケットをこまめに立てるのはいいこと。でも、実装に結びつかないまま放置されたチケットが積み上がっていくと、 それはもはや「バックログ管理」ではなく「チケットの墓場」になってしまいます。
この問題を解決するために、チケットを作った瞬間にPRが自動で生まれる仕組みを作ってみました。 Issueを作るだけでPRが自動生成され、開発者はレビューに集中できます。
今回は、そんな「チケット駆動開発の自動化」の仕組みをリポジトリを跨いでPoCとして実装してみた話を書きます。
今回実現したいこと
今回はGitHubのProjectでIssueを作成したときに、そのIssueを元に別リポジトリに存在するソースコードを修正するPRの自動生成を行います。
自動生成の流れ
- 手動でリポジトリAにIssueを作成
- 手動でIssueに
ready-for-copilotラベルを付与 - 2をトリガーにリポジトリAのGitHub Actionsが起動
- Github Actionにより、リポジトリBにIssueを作成
- Github Actionにより、リポジトリBに作成したIssueにGithub Copilotをアサイン
- Githubの機能により、5をトリガーに自動でPull RequestがリポジトリBに作成される
リポジトリA, Bの役割を整理すると下記のようになります。
- リポジトリA(チケット起票側)
- チケット起票
- Github Action実行用のymlファイル管理
- Github Action実行
- リポジトリB(ソースコードが置いてあるリポジトリ)
- PR作成
なぜこのようなリポジトリを跨ぐ構成になったかというと、以下のような運用をしているためです:
リポジトリA:チーム専用のプロジェクト管理リポジトリ
- GitHub Projectでのバックログ管理
- スクラム関連のドキュメント
- チーム独自の自動化ツール
リポジトリB:実際のプロダクトコードが存在するリポジトリ
- 複数チームが共同で開発
- 厳格なレビュープロセス
このような分離により、チームの自由度を保ちながら、プロダクトコードの品質管理の維持に努めています。そこで、リポジトリAでのIssue管理からリポジトリBでのPR作成を自動化する、今回のような仕組みを実装しました。
実現するための前提条件
本記事を参考に実際に試す場合、Githubの機能を利用するため、下記を満たしていないと実現できません。
Github Actionsを利用できるリポジトリが最低一つあること Github Copilotを利用できるリポジトリが最低一つあること
やってみる
それでは実際にやってみましょう
ステップ1:Issue作成で起動するワークフローを作成する
まず、リポジトリAでGithub Actionsの基本構造を定義します。
.github/workflowsにauto_copilot_pr.ymlを作成します。
これでリポジトリAでIssueを作成した時に動くGithub Workflowの内容を定義しています。
name: Auto assign Copilot Coding Agent on: issues: types: [opened, labeled] jobs: handle-issue: name: Create Issue in repository-b and assign to Copilot runs-on: ubuntu-latest if: (github.event.action == 'opened' || github.event.action == 'labeled') && (github.event.label.name == 'ready-for-copilot') steps: - name: Check GitHub CLI version run: gh --version
ステップ2:認証情報設定
今回のGithub Actionsを実行するにあたり、リポジトリの操作権限をワークフローに与えるためのPersonal Access Token(PAT)が必要になるので、発行してください。
発行するPATには下記の権限を付与しておきます
- アクセス可能なリポジトリ
- Repository A
- Repository B
- Permission
- Issues: Read and Write
- Metadata: Read-only
発行したPATはリポジトリAのActions Secretsに登録します。

ステップ3:Issue情報の抽出と環境変数への保存
次に、STEP2でActions Secretsに設定した認証情報を利用してIssue情報を環境変数として記録する処理を記述します。
注意:GH_PATはActions Secretsに設定したRepository Secrets名に合わせて変更してください。
ここでは、リポジトリAでIssueを作るたびにリポジトリBにIssueが作成されないように、ready-for-copilotというラベルが付与されたissueに対してのみ、本ロジックが動くように制御しています。
name: Auto assign Copilot Coding Agent
on:
issues:
types: [opened, labeled]
jobs:
handle-issue:
name: Create Issue in repository-b and assign to Copilot
runs-on: ubuntu-latest
if: (github.event.action == 'opened' || github.event.action == 'labeled') && (github.event.label.name == 'ready-for-copilot')
env:
GH_TOKEN: ${{ secrets.GH_PAT }}
steps:
- name: Extract issue info
id: issue_data
run: |
echo "title<<EOF" >> "$GITHUB_OUTPUT"
echo "${GITHUB_EVENT_TITLE}" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
echo "body<<EOF" >> "$GITHUB_OUTPUT"
echo "${GITHUB_EVENT_BODY}" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
echo "number=${GITHUB_EVENT_NUMBER}" >> "$GITHUB_OUTPUT"
shell: bash
env:
GITHUB_EVENT_TITLE: ${{ github.event.issue.title }}
GITHUB_EVENT_BODY: ${{ github.event.issue.body }}
GITHUB_EVENT_NUMBER: ${{ github.event.issue.number }}
- name: Check GitHub CLI version
run: gh --version
ステップ4:リポジトリBへのIssue作成
記録したIssue情報を使って、リポジトリBに新規Issueを作成します。
注意:owner-orgの部分は、実際にリポジトリを所有している組織名やユーザー名に置き換えてください
name: Auto assign Copilot Coding Agent
on:
issues:
types: [opened, labeled]
jobs:
handle-issue:
name: Create Issue in repository-b and assign to Copilot
runs-on: ubuntu-latest
if: (github.event.action == 'opened' || github.event.action == 'labeled') && (github.event.label.name == 'ready-for-copilot')
env:
GH_TOKEN: ${{ secrets.GH_PAT }}
steps:
- name: Extract issue info
id: issue_data
run: |
echo "title<<EOF" >> "$GITHUB_OUTPUT"
echo "${GITHUB_EVENT_TITLE}" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
echo "body<<EOF" >> "$GITHUB_OUTPUT"
echo "${GITHUB_EVENT_BODY}" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
echo "number=${GITHUB_EVENT_NUMBER}" >> "$GITHUB_OUTPUT"
shell: bash
env:
GITHUB_EVENT_TITLE: ${{ github.event.issue.title }}
GITHUB_EVENT_BODY: ${{ github.event.issue.body }}
GITHUB_EVENT_NUMBER: ${{ github.event.issue.number }}
- name: Check GitHub CLI version
run: gh --version
- name: Create corresponding issue in repository-b
id: new_issue
env:
REPO_B_TITLE: ${{ steps.issue_data.outputs.title }}
REPO_B_NUMBER: ${{ steps.issue_data.outputs.number }}
run: |
gh issue create \
--repo owner-org/repository-b \
--title "$REPO_B_TITLE" \
--body $'Originally from:\n- owner-org/repository-a#'"${REPO_B_NUMBER}" > created_issue.txt
cat created_issue.txt
ISSUE_URL=$(grep -Eo 'https://github.com/[^ ]+/issues/[0-9]+' created_issue.txt | head -n 1)
echo "url=$ISSUE_URL" >> "$GITHUB_OUTPUT"
- name: Get new issue number from URL
id: issue_number
run: |
issue_number=$(echo "${{ steps.new_issue.outputs.url }}" | grep -oE '[0-9]+$')
echo "number=$issue_number" >> "$GITHUB_OUTPUT"
ステップ5:GraphQL APIでGithub Copilotを検索してIssueにアサインする
Github Copilotのユーザー情報と作成したIssueを取得し、Github CopilotをIssueにアサインします。
注意:copilot-agentの部分は、実際のCopilot Botのログイン名に置き換えてください
name: Auto assign Copilot Coding Agent
on:
issues:
types: [opened, labeled]
jobs:
handle-issue:
name: Create Issue in repository-b and assign to Copilot
runs-on: ubuntu-latest
if: (github.event.action == 'opened' || github.event.action == 'labeled') && (github.event.label.name == 'ready-for-copilot')
env:
GH_TOKEN: ${{ secrets.GH_PAT }}
steps:
- name: Extract issue info
id: issue_data
run: |
echo "title<<EOF" >> "$GITHUB_OUTPUT"
echo "${GITHUB_EVENT_TITLE}" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
echo "body<<EOF" >> "$GITHUB_OUTPUT"
echo "${GITHUB_EVENT_BODY}" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
echo "number=${GITHUB_EVENT_NUMBER}" >> "$GITHUB_OUTPUT"
shell: bash
env:
GITHUB_EVENT_TITLE: ${{ github.event.issue.title }}
GITHUB_EVENT_BODY: ${{ github.event.issue.body }}
GITHUB_EVENT_NUMBER: ${{ github.event.issue.number }}
- name: Check GitHub CLI version
run: gh --version
- name: Create corresponding issue in repository-b
id: new_issue
env:
REPO_B_TITLE: ${{ steps.issue_data.outputs.title }}
REPO_B_NUMBER: ${{ steps.issue_data.outputs.number }}
run: |
gh issue create \
--repo owner-org/repository-b \
--title "$REPO_B_TITLE" \
--body $'Originally from:\n- owner-org/repository-a#'"${REPO_B_NUMBER}" > created_issue.txt
cat created_issue.txt
ISSUE_URL=$(grep -Eo 'https://github.com/[^ ]+/issues/[0-9]+' created_issue.txt | head -n 1)
echo "url=$ISSUE_URL" >> "$GITHUB_OUTPUT"
- name: Get new issue number from URL
id: issue_number
run: |
issue_number=$(echo "${{ steps.new_issue.outputs.url }}" | grep -oE '[0-9]+$')
echo "number=$issue_number" >> "$GITHUB_OUTPUT"
- name: Get Copilot user ID
id: get_copilot_id
run: |
copilot_id=$(gh api graphql -f query='
query {
repository(owner: "owner-org", name: "repository-b") {
suggestedActors(capabilities: [CAN_BE_ASSIGNED], first: 10) {
nodes {
login
... on Bot {
id
}
}
}
}
}' | jq -r '.data.repository.suggestedActors.nodes[] | select(.login == "copilot-agent") | .id')
echo "id=$copilot_id" >> $GITHUB_OUTPUT
- name: Get new issue global node ID
id: get_issue_node_id
run: |
issue_node_id=$(gh api graphql -f query='
query {
repository(owner: "owner-org", name: "repository-b") {
issue(number: ${{ steps.issue_number.outputs.number }}) {
id
}
}
}' | jq -r '.data.repository.issue.id')
echo "id=$issue_node_id" >> "$GITHUB_OUTPUT"
- name: Assign Copilot to issue
run: |
gh api graphql -f query='
mutation {
replaceActorsForAssignable(input: {
assignableId: "${{ steps.get_issue_node_id.outputs.id }}",
actorIds: ["${{ steps.get_copilot_id.outputs.id }}"]
}) {
assignable {
... on Issue {
assignees(first: 5) {
nodes {
login
}
}
}
}
}
}'
完成です!
ここまできたら、あとはリポジトリAでIssueを作成して、ready-for-copilotラベルを付与してあげれば、自動でリポジトリBにプルリクが生成されているはずです。
今後の課題
ここまでで一旦PoCとして最低限の、Issueを作成して別リポジトリにプルリクを出すことができるようになりました。
しかし、Githubの機能に依存している箇所が多かったり、自動生成されるプルリクの精度など、実運用に至らせるためにはまだまだ改善の余地がたくさんあったため、見つけた課題を述べていきます。
ブランチ名の制約
ブランチ名にcopilot/というプレフィックスが自動的に付与されてしまいます。
実際の運用ルールではブランチ命名規則が定められている現場もあると思います。そのような場合に、今回は対応できません。
Githubのみで完結するため、基本的にできることはGithubが提供してくれいている機能に制限されます。現在Github Copilotを利用して生成したプルリクのブランチ名を変えられる機能やGithub Commandは用意されていません。
プルリクの精度
自動生成されるプルリクエストはGithub Copilotが生成するため、精度はGithub Copilotの性能に依存します。
Github CopilotをアサインするIssueの書き方も最適化する余地が残っています。
今回試した範囲では、簡単なカラム追加や数行のコード追加レベルであれば十分、そのままプロダクションコードにマージできそうでしたが、高度なドメイン知識が求められるような複雑な内容は難しそうです。 Github Copilotにcontextを食わせる上手なやり方があれば、もっと精度を上げられると思います。
他のツールとの比較
「そもそもDevinとかでよくない?」という声もあるでしょう。 それはそう。MPCとか使って、JIRAやDevinを連携すれば自動化できると思います。 ただ、今回は複数のサービスを利用せず、Githubのみで完結している点が嬉しいです。
まとめ
今回は、GitHub Issueから自動でPRを作成する仕組みを実装しました。完璧な自動化とはいきませんが、簡単なタスクであれば十分実用的なレベルのPRが作成できることが確認できました。 この仕組みにより、開発者は「実装」ではなく「レビュー」に集中でき、バックログの消化速度向上が期待できます。 皆さんの開発現場でも、このような自動化の仕組みを導入してみてはいかがでしょうか。