目次
問題の状況
Yarn Workspacesベースのモノレポでは、Dependabotがpackage.jsonのみを更新しyarn.lockを更新しないという問題があります。これを解決するため、DependabotのPRでyarn.lockファイルを自動更新するワークフローを運用していました。このワークフローはlockfileを更新してコミットをpushしますが、push後にCIワークフロー(lint、build、testなど)が再実行されない問題が発生しました。
モノレポでのDependabot運用については、以前の記事をご参照ください。
問題となったワークフローの核心部分は以下の通りです。
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
token: ${{ secrets.GITHUB_TOKEN }} # このトークンでpushすると他のワークフローがトリガーされない
# ... yarn.lockの更新 ...
- name: Commit updated lockfile
run: |
git add yarn.lock
git diff --staged --quiet || git commit -m "chore: update yarn.lock"
git push # このpushがCIをトリガーしない
原因
GitHub Actionsでデフォルトで提供されるGITHUB_TOKENでpushすると、他のワークフローがトリガーされません。 これはGitHubの意図的な設計であり、ワークフロー間の無限再帰実行を防止するためです。
When you use the repository’s
GITHUB_TOKENto perform tasks, events triggered by theGITHUB_TOKENwill not create a new workflow run.
つまり、GITHUB_TOKENでpushしたコミットはpushイベントやpull_requestイベントのsynchronizeタイプを発生させないため、CIワークフローが実行されません。
解決方法の比較
この問題を解決するには、GITHUB_TOKENの代わりに別の認証手段を使用する必要があります。代表的なものとしてPAT(Personal Access Token)とGitHub Appsがあります。
| 項目 | PAT | GitHub Apps |
|---|---|---|
| 所有者 | 個人 | Organization |
| 権限範囲 | ユーザーのアクセス権限に依存 | アプリに設定した権限のみ |
| トークン寿命 | 手動設定(最大1年) | 自動発行、1時間後に失効 |
| 退職リスク | あり | なし |
| 監査追跡 | 個人アカウントベース | Bot専用アカウントとして表示 |
| Rate Limit | 個人アカウント共有 | アプリ専用(より高い制限) |
方法1: PAT (Personal Access Token)
最も素早く適用できる方法です。
設定方法
PATは以下の手順で作成・設定できます。
- GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens
- Generate new tokenをクリック
- 設定:
- Resource owner: Organizationを選択
- Repository access: 対象のrepositoryのみを選択
- Permissions:
- Contents: Read and write
- Pull requests: Read and write(必要な場合)
- 生成されたトークンをRepositoryのSettings → Secrets → Actionsに登録
ワークフローの修正
このように作成・設定したPATをactions/checkoutに渡すことで、push時に他のワークフローが正常にトリガーされます。
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
token: ${{ secrets.PAT_TOKEN }} # PATに変更
PATの欠点
PATは素早く問題を解決できますが、本番環境で長期的に運用するには以下のようなリスクがあります。
| 項目 | 説明 |
|---|---|
| 個人アカウント依存 | トークン所有者が退職またはアカウントが無効化されるとCIが停止する |
| 広い権限範囲 | Fine-grainedで制限しても個人のアクセス権限に依存する |
| 手動更新が必要 | 有効期限を設定すると定期的な更新が必要 |
| 監査追跡 | コミットが個人アカウントベースで追跡される |
方法2: GitHub Apps(推奨)
Organization所有のGitHub Appを作成してトークンを発行する方法です。PATの欠点をすべて解消でき、本番環境で推奨される方式です。
GitHub Apps設定ガイド
GitHub Appsの設定手順は以下の通りです。
Step 1: GitHub Appの作成
OrganizationのGitHub Appを作成するには、Organization Owner権限が必要です。権限がある場合は以下の手順に従って進めます。
- GitHubでOrganizationページに移動
- Settings → Developer settings → GitHub Apps → New GitHub App
入力項目:
| 項目 | 値 | 説明 |
|---|---|---|
| GitHub App name | my-ci-bot | Organization内でユニークな名前 |
| Homepage URL | Repository URL | 必須だが内部利用のためrepo URLで十分 |
| Webhook | Activeのチェックを外す | CIトークン発行用なのでWebhookは不要 |
Permissions設定(最小権限の原則)
今回の問題を解決するために必要な最小権限は以下の通りです。残りの権限はすべて不要なため、最小権限の原則に従い必要な権限のみを付与します。
Repository permissions:
| Permission | Access | 用途 |
|---|---|---|
| Contents | Read and write | コードのpush |
| Metadata | Read-only | 自動選択 |
| Pull requests | Read and write | PR関連の操作が必要な場合 |
Organization permissions / Account permissions: すべて不要
インストール範囲
今回の問題は特定のrepositoryで発生する問題のため、GitHub Appのインストール範囲をOnly on this accountに制限します。これによりAppが他のrepositoryにインストールされることを防止できます。
- Where can this GitHub App be installed? →
Only on this account
Create GitHub Appをクリックして作成を完了します。
Step 2: Private Keyの生成
GitHub Appを使用するにはApp IDとPrivate Keyが必要です。App IDはApp設定ページ上部で確認でき、Private Keyは以下の手順で生成できます。
- 作成したApp設定ページでApp IDをメモ(ページ上部に表示)
- ページ下部のPrivate keysセクション → Generate a private keyをクリック
.pemファイルが自動ダウンロード
この2つの値(App ID、Private Key)が、以降のワークフローでトークンを発行する際に使用されます。
Step 3: RepositoryへのAppインストール
作成したGitHub Appを実際に使用するrepositoryにインストールする必要があります。以下の手順でGitHub Appをインストールします。
- App設定ページ左メニュー → Install App
- Organizationを選択 → Install
- Only select repositories → 対象repositoryを選択
- Installをクリック
Step 4: Secretsの登録
App IDとPrivate KeyをGitHub Actionsで使用できるようにSecretsとして登録します。
Repository Settings → Secrets and variables でActionsとDependabotのそれぞれに同じSecretを登録します。
- Actions: Settings → Secrets and variables → Actions → New repository secret
- Dependabot: Settings → Secrets and variables → Dependabot → New repository secret
| Secret Name | 値 |
|---|---|
MY_CI_BOT_APP_ID | Step 2で確認したApp ID |
MY_CI_BOT_PRIVATE_KEY | .pemファイルの全内容 |
なぜ両方に登録する必要があるのか?
GitHub Actionsはワークフローをトリガーした主体によって参照するsecrets格納場所が異なります。Dependabotがトリガーしたワークフローはサプライチェーン攻撃防止のため制限されたセキュリティコンテキストで実行され、Dependabot secretsのみを参照できます。一方、通常のイベント(手動push、一般的なPR、workflow_dispatchなど)でトリガーされたワークフローはActions secretsを参照します。したがって、同一のワークフローがどの主体によってトリガーされても正常に動作するためには、両方に登録する必要があります。
.pemファイルの内容は以下のような形式です。全体をそのままコピーして登録します。
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA...
...
-----END RSA PRIVATE KEY-----
複数のrepositoryで同じAppを使用する場合、Organization Settings → Secretsに登録すれば共有できます。
Step 5: ワークフローの修正
actions/create-github-app-tokenアクションを使用して、ランタイムにトークンを生成します。
steps:
# 1. checkoutはGITHUB_TOKEN(read)で — トークンを.git/configに残さない
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
persist-credentials: false
# 2. 依存関係のインストール(この時点で.git/configにcredentialなし → 安全)
- name: Setup node
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- name: Update lockfile
run: yarn install --no-immutable
# 3. push直前にのみAppトークンを生成 → 露出時間を最小化
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.MY_CI_BOT_APP_ID }}
private-key: ${{ secrets.MY_CI_BOT_PRIVATE_KEY }}
# 4. トークンでremote URLを設定してpush
- name: Commit and push
run: |
git config user.name "my-ci-bot[bot]"
git config user.email "<APP_ID>+my-ci-bot[bot]@users.noreply.github.com"
git remote set-url origin "https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/${{ github.repository }}.git"
git add .
git diff --staged --quiet || git commit -m "chore: automated update"
git push
重要なポイントは以下の通りです。
actions/create-github-app-token@v1がApp IDとPrivate Keyで**インストールトークン(installation token)**を発行- このトークンは約1時間有効で、ワークフロー実行ごとに新しく発行される
- このトークンでpushすると他のワークフローが正常にトリガーされる
- git commitのauthorは
<app-name>[bot]形式を使用
セキュリティ: トークン露出範囲を最小化する構成
セキュリティのためには、単純にGITHUB_TOKENをGitHub Appトークンに置き換えるだけでは不十分です。トークンが露出する時間と範囲も考慮する必要があります。
actions/checkoutはデフォルトでpersist-credentials: trueのため、渡されたトークンを.git/configのextraheaderに保存します。もしAppトークンでcheckoutした後にyarn installを実行すると、依存パッケージのpost-install scriptが.git/configを読み取り、write権限を持つトークンを窃取する可能性があります。
Dependabot PRは外部パッケージを更新するものであるため、このサプライチェーン攻撃シナリオに特に脆弱です。
[危険な構成]
checkout (App token) → .git/configにトークン保存 → yarn install → post-install scriptがトークンを窃取可能
[安全な構成]
checkout (persist-credentials: false) → .git/configにトークンなし → yarn install (安全) → Appトークン生成 → push
これを防止するために以下のような構成を推奨します。
persist-credentials: false: checkout時に.git/configにcredentialを保存しない- トークン生成をpush直前に移動:
yarn installなど外部コード実行の後にトークンを生成 git remote set-urlでpush時にのみトークンを使用: トークンが必要な瞬間にのみ設定
permissionsの設定に関する注意点
GitHub Appトークンに切り替えると、ジョブのpermissionsブロックも見直す必要があります。permissionsはGITHUB_TOKENに対する権限設定なので、ジョブ内でGITHUB_TOKENを使用しない場合はcontents: writeを付与する必要はありません。
# Before: GITHUB_TOKENでpushするためwrite必要
permissions:
contents: write
# After: pushはGitHub Appトークンが担当するためreadで十分
permissions:
contents: read
最小権限の原則に従い、実際に使用しないトークンに不要な権限を付与しないようにすることがセキュリティ強化に役立ちます。
テスト方法
GitHub Appトークンが正しく動作するか確認するため、以下のようなテストワークフローを作成して検証できます。
name: Test GitHub App Token
on:
push:
branches:
- test/app-token # テスト用ブランチでのみ実行
jobs:
test-token:
name: Test App Token
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.MY_CI_BOT_APP_ID }}
private-key: ${{ secrets.MY_CI_BOT_PRIVATE_KEY }}
- name: Verify token was generated
run: |
if [ -z "${{ steps.app-token.outputs.token }}" ]; then
echo "::error::Token generation failed"
exit 1
fi
echo "Token generated successfully"
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ steps.app-token.outputs.token }}
- name: Test push (empty commit on temp branch)
run: |
git config user.name "my-ci-bot[bot]"
git config user.email "<APP_ID>+my-ci-bot[bot]@users.noreply.github.com"
git checkout -b test/app-token-verify
git commit --allow-empty -m "test: verify GitHub App token push"
git push origin test/app-token-verify
echo "Push succeeded"
- name: Cleanup temp branch
if: always()
run: git push origin --delete test/app-token-verify || true
テスト手順
このテストワークフローをtest/app-tokenブランチに追加してpushすると、以下の過程で設定が正しいか検証できます。
- 上記ワークフローファイルを
test/app-tokenブランチに追加してpush - Actionsタブでワークフロー実行結果を確認
- 各ステップの成功・失敗で設定を検証:
- Generate token成功 → Secrets設定が正しい
- Checkout成功 → トークンでrepoにアクセス可能
- Push成功 → トークンでpush可能
- テスト完了後にワークフローファイルを削除
トラブルシューティング
| エラーメッセージ | 原因 | 解決方法 |
|---|---|---|
could not create workflow dispatch event: HTTP 404 | Appがrepositoryにインストールされていない | Step 3のInstall Appを確認 |
The permissions requested are not granted to this installation | Appの権限不足 | App Settings → Permissionsで権限を追加 |
Could not resolve to a valid ref | Private Keyが間違っている | .pemファイル全内容がSecretに登録されているか確認 |
Resource not accessible by integration | インストール範囲の問題 | Appが対象repositoryにインストールされているか確認 |
まとめ
GitHub ActionsでGITHUB_TOKENを使ってpushすると他のワークフローがトリガーされないのは、無限再帰を防止するための意図的な設計です。これを解決する方法は大きく2つあります。
| 方法 | 適した状況 |
|---|---|
| PAT | 素早い一時的な解決が必要な場合 |
| GitHub Apps | 本番環境で推奨される方法 |
GitHub Appsは初期設定にOrganization Owner権限が必要という参入障壁がありますが、一度設定すれば個人に依存しない安全で管理しやすいトークン管理が可能です。特にDependabot PRのように外部パッケージを扱うワークフローでは、トークン露出範囲を最小化する構成まで考慮することをお勧めします。
参考資料
- GitHub Docs: Triggering a workflow from a workflow
- GitHub Docs: Registering a GitHub App
- actions/create-github-app-token
私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!
アプリ広報
Dekuが開発したアプリを使ってみてください。Dekuが開発したアプリはFlutterで開発されています。興味がある方はアプリをダウンロードしてアプリを使ってくれると本当に助かります。