make-github-actions-workflow について
リポジトリの規約に沿った GitHub Actions ワークフローを作成するスキルです。CI やリポジトリ自動化に向け、イベント、権限、安全なスクリプト、条件、エラー処理、検証を案内します。
主な機能
- 01ワークフローのトリガーイベント選択
- 02明示的で最小限の権限設定
- 03実行条件と冪等性チェックの追加
- 04複雑な処理に `actions/github-script@v8` を使用
- 05API エラーとページネーションの処理
- 06ワークフローの安全性と動作の検証
活用シーン
- 01CI 検証ワークフローの作成
- 02プルリクエスト管理の自動化
- 03Issue ラベル付けや通知の追加
- 04ワークフローの権限と安全性の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dotnet/ efcore --skill make-github-actions-workflowインストール後、Claude Code で/make-github-actions-workflowを入力して読み込まれたか確認します。
詳細
GitHub Actions ワークフローの作成
このリポジトリの規約やパターンに従った GitHub Actions ワークフローを作成して設定します。ワークフローを使うと、CI 検証、PR 管理、Issue のラベル付け、通知などのタスクを自動化できます。
リポジトリの規約
このリポジトリでは、すべてのワークフローで特定のパターンを使用しています。新しいワークフローを作成する際は、次の規約に従ってください。
ファイルの場所と命名
- すべてのワークフローは
.github/workflows/に配置します - kebab-case の名前を使用します:
label-and-milestone-issues.yml、validate-pr-target-branch.yml - ワークフローの目的を説明するコメントブロックから始めます
ランナー
- 汎用的なジョブには
ubuntu-latestを使用します - 特定の OS バージョンが必要な場合(copilot のセットアップ手順など)は
ubuntu-24.04を使用します
権限
- 必ず明示的に権限を宣言します。デフォルトの権限には依存しないでください
- 最小限の権限を使用します。ワークフローに必要な権限のみをリクエストしてください
- 一般的なパターン:
- 読み取り専用:
permissions: {} - PR コメント:
pull-requests: write - Issue 管理:
issues: write - コードの読み取り:
contents: read
- 読み取り専用:
- 外部コントリビューターからの PR に書き込みアクセスが必要なワークフローでは、
pull_requestの代わりにpull_request_targetを使用します- セキュリティに関する警告:
pull_request_targetワークフローは、ベースリポジトリのコンテキストとその権限で実行されます。これらのワークフローでは、PR ブランチのコードをチェックアウトしたり実行したりしないでください。代わりに、イベントペイロードや GitHub API(actions/github-script、github.rest.*など)を使って、PR を安全に調査し操作してください。
- セキュリティに関する警告:
スクリプト作成
- 複雑なロジックには、シェルスクリプトではなく
actions/github-script@v8を使用します async/awaitを使って JavaScript を記述します。githubオブジェクト(Octokit)とcontextを利用できます- REST API 呼び出しには
github.rest.*を、GraphQL クエリにはgithub.graphql()を使用します context.payload.pull_request、context.repo.owner、context.repo.repoを使って PR や Issue のデータにアクセスします- デバッグには
console.log()を使用し、エラーにはcore.setFailed()を使用します
エラー処理
- API 呼び出しを try/catch ブロックで囲みます
- 最初のエラーで失敗させるのではなく、エラーを収集して最後に報告します
- スキップした操作は、その理由とともにログに記録します
ワークフロー
ステップ 1:要件を把握する
ワークフローを記述する前に、次の点を明確にします。
- どのイベントをトリガーにしますか?(push、pull_request、check_suite、schedule など)
- どの権限が必要ですか?
- 実行を制限する条件は何ですか?(作成者、ブランチ、ラベルなど)
- どの API 呼び出しが必要ですか?
- レート制限や冪等性に関する考慮事項はありますか?
ステップ 2:トリガーイベントを選択する
| ユースケース | イベント | 備考 |
|---|---|---|
| PR の作成または更新 | pull_request | PR への読み取り専用アクセス |
| 書き込みアクセスが必要な PR | pull_request_target | コメント、ラベル付け、クローズが可能 |
| PR のマージ | pull_request_target: [closed] | github.event.pull_request.merged == true を確認します |
| CI チェックの完了 | check_suite: [completed] | 自身の実行をスキップするには app.slug != 'github-actions' でフィルタリングします |
| コードのプッシュ | push | ブランチやパスでフィルタリングします |
| 手動トリガー | workflow_dispatch | パラメーターには inputs: を追加します |
| スケジュール実行 | schedule | cron 構文を使用します |
ステップ 3:ワークフローを記述する
次のテンプレートに従って YAML ファイルを作成します。
# Description of what this workflow does and why.
name: Descriptive Workflow Name
on:
<trigger_event>:
types: [<event_types>]
permissions:
<resource>: <read|write>
jobs:
<job-name>:
if: <guard_condition> # Optional: skip when not needed
runs-on: ubuntu-latest
steps:
- name: Descriptive step name
uses: actions/github-script@v8
with:
script: |
// Your logic here
ステップ 4:ガード条件を実装する
不要な実行を避けるため、条件を追加します。
- イベントペイロードを確認するジョブレベルの
if:(例:github.event.check_suite.app.slug != 'github-actions') - PR の作成者やドラフト状態などを確認する、スクリプト内での早期
return - 冪等性チェック(例:アクションの重複を防ぐタグとして、非表示の HTML コメントを使用する)
opened、edited、reopened に反応する pull_request_target ポリシーワークフローの場合:
editedでは、context.payload.changes?.baseが存在しない場合に処理を終了します。タイトルや本文の編集によって、ターゲットブランチのポリシーを再適用してはいけません。- PR の作成者だけでなく、
context.actorの権限を確認します。メンテナーが外部からの PR を編集または再オープンすることがあり、その操作にはメンテナーの権限を適用する必要があります。 openedでは、アクターと作成者は同じなので、権限を一度だけ照会し、その結果をポリシーとラベル付けの判断に再利用します。- アクターまたは作成者のいずれかがボットの場合は、対象外にします。
mainなど、許可するブランチを 1 つだけ指定するのではなく、禁止するブランチのパターン(例:release/*)を直接指定します。- 権限の照会に失敗した場合は、破壊的な操作をせずに処理を終了します。アクセス権が不明であることは、コントリビューターが外部のユーザーである証拠にはなりません。
ステップ 5:検証する
ワークフローを作成したら、以下を確認します。
- 権限は最小限で、明示的に宣言されています
- 複雑なロジックには
actions/github-script@v8を使用しています - ガード条件によって不要な実行を防いでいます
- API 呼び出しでは必要に応じてページネーションに対応しています(
per_page: 100を使用) - エラー処理で失敗を暗黙に無視していません
- シークレットやトークンをハードコードしていません
- ワークフローによって無限ループが発生しません(例:自分自身をトリガーするワークフロー)
このリポジトリの既存のワークフロー
参考として、以下のワークフローを確認してください。
| ワークフロー | トリガー | 目的 |
|---|---|---|
validate-pr-target-branch.yml | pull_request_target: [opened, edited, reopened] | リリースブランチをターゲットとする外部 PR をクローズし、コミュニティラベルを追加します |
label-and-milestone-issues.yml | pull_request_target: [closed] | 修正用 PR がマージされたときに、課題にラベルとマイルストーンを設定します |
copilot-setup-steps.yml | workflow_dispatch、push、pull_request | Copilot の開発環境をセットアップします |
inter-branch-merge-flow.yml | release/** への push | arcade を介したブランチ間マージをトリガーします |
よく使われるパターン
PR にコメントする
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: 'Comment text'
});
ユーザーの権限を確認する
const { data: permissions } = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username: context.actor
});
const hasWriteAccess = ['admin', 'write'].includes(permissions.permission);
ボットが所有するコメントを冪等に更新する
非表示のマーカーは、ワークフローのコメントを識別するためのものですが、所有者を示すものではありません。コントリビューターやほかのアプリも、自分のコメントに同じマーカーを付けられるためです。マーカーと正確な github-actions[bot] ログイン名の両方で照合し、そのコメントを更新するか、新しいコメントを作成します。一般的な user.type === 'Bot' チェックでは、ワークフローの所有者を特定できません。ワークフローで固定されているアクションの SHA を維持し、pull_request_target ジョブではプルリクエストのフォークからコードを実行しないでください。
const marker = '';
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
per_page: 100
});
const body = `${marker}\nComment text`;
const existing = comments.find(comment =>
comment.user?.login === 'github-actions[bot]' && comment.body?.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body
});
}
特定のコミットにおけるリポジトリファイルを読み込む
const { data: fileData } = await github.rest.repos.getContent({
owner, repo,
path: 'eng/Versions.props',
ref: commitSha
});
const content = Buffer.from(fileData.content, 'base64').toString('utf-8');