Table of Contents
The Problem
In a monorepo based on Yarn Workspaces, Dependabot only updates package.json without regenerating yarn.lock. To solve this, we had a workflow that automatically updates the yarn.lock file on Dependabot PRs. This workflow updates the lockfile and pushes a commit, but after the push, CI workflows (lint, build, test, etc.) were not re-triggered.
For more details on running Dependabot in a monorepo, see the previous posts.
The core part of the problematic workflow looked like this:
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
token: ${{ secrets.GITHUB_TOKEN }} # Pushing with this token won't trigger other workflows
# ... update yarn.lock ...
- name: Commit updated lockfile
run: |
git add yarn.lock
git diff --staged --quiet || git commit -m "chore: update yarn.lock"
git push # This push doesn't trigger CI
Root Cause
When you push using the default GITHUB_TOKEN provided by GitHub Actions, other workflows will not be triggered. This is an intentional design by GitHub to prevent infinite recursive execution between workflows.
When you use the repository’s
GITHUB_TOKENto perform tasks, events triggered by theGITHUB_TOKENwill not create a new workflow run.
In other words, commits pushed with GITHUB_TOKEN do not generate push events or the synchronize type of pull_request events, so CI workflows are not executed.
Comparing Solutions
To solve this problem, you need to use a different authentication method instead of GITHUB_TOKEN. The two main options are PAT (Personal Access Token) and GitHub Apps.
| Item | PAT | GitHub Apps |
|---|---|---|
| Owner | Individual | Organization |
| Permission Scope | Tied to user’s access rights | Only permissions set on the app |
| Token Lifetime | Manual (up to 1 year) | Auto-issued, expires in 1 hour |
| Offboarding Risk | Yes | None |
| Audit Trail | Personal account-based | Shown as dedicated bot account |
| Rate Limit | Shared with personal account | App-dedicated (higher limits) |
Option 1: PAT (Personal Access Token)
This is the quickest method to apply.
Setup
PATs can be created and configured as follows:
- GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens
- Click Generate new token
- Configure:
- Resource owner: Select your Organization
- Repository access: Select only the target repository
- Permissions:
- Contents: Read and write
- Pull requests: Read and write (if needed)
- Register the generated token in Repository Settings → Secrets → Actions
Workflow Update
Pass the PAT to actions/checkout, and other workflows will be triggered normally on push.
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
token: ${{ secrets.PAT_TOKEN }} # Changed to PAT
Drawbacks of PATs
While PATs can quickly resolve the issue, they carry risks for long-term use in production:
| Item | Description |
|---|---|
| Personal account dependency | CI breaks if the token owner leaves or the account is deactivated |
| Broad permission scope | Even with fine-grained tokens, permissions are tied to the individual |
| Manual renewal required | Tokens with expiration dates need periodic renewal |
| Audit trail | Commits are tracked under the personal account |
Option 2: GitHub Apps (Recommended)
This method involves creating an Organization-owned GitHub App to issue tokens. It addresses all the drawbacks of PATs and is the recommended approach for production environments.
GitHub Apps Setup Guide
Here is the step-by-step process for setting up GitHub Apps.
Step 1: Create a GitHub App
Creating an Organization’s GitHub App requires Organization Owner permissions. If you have the required permissions, follow these steps:
- Navigate to your Organization page on GitHub
- Settings → Developer settings → GitHub Apps → New GitHub App
Input fields:
| Field | Value | Description |
|---|---|---|
| GitHub App name | my-ci-bot | Unique name within the Organization |
| Homepage URL | Repository URL | Required, but the repo URL suffices for internal use |
| Webhook | Uncheck Active | Not needed since this is for CI token issuance |
Permissions (Principle of Least Privilege)
The minimum permissions required to solve this problem are as follows. All other permissions are unnecessary, so only grant what is needed:
Repository permissions:
| Permission | Access | Purpose |
|---|---|---|
| Contents | Read and write | Push code |
| Metadata | Read-only | Automatically selected |
| Pull requests | Read and write | If PR-related operations needed |
Organization permissions / Account permissions: None required
Installation Scope
Since this problem occurs in a specific repository, restrict the GitHub App’s installation scope to Only on this account. This prevents the App from being installed on other repositories.
- Where can this GitHub App be installed? →
Only on this account
Click Create GitHub App to complete the creation.
Step 2: Generate a Private Key
To use a GitHub App, you need an App ID and a Private Key. The App ID can be found at the top of the App settings page, and the Private Key can be generated as follows:
- Note the App ID on the App settings page (displayed at the top)
- Scroll to the Private keys section at the bottom → Click Generate a private key
- A
.pemfile will be automatically downloaded
These two values (App ID and Private Key) will be used to issue tokens in the workflow.
Step 3: Install the App on the Repository
The created GitHub App must be installed on the repository where it will be used. Follow these steps:
- App settings page left menu → Install App
- Select the Organization → Install
- Only select repositories → Select the target repository
- Click Install
Step 4: Register Secrets
Register the App ID and Private Key as Secrets so they can be used in GitHub Actions.
Repository Settings → Secrets and variables — register the same Secrets in both Actions and Dependabot.
- Actions: Settings → Secrets and variables → Actions → New repository secret
- Dependabot: Settings → Secrets and variables → Dependabot → New repository secret
| Secret Name | Value |
|---|---|
MY_CI_BOT_APP_ID | App ID from Step 2 |
MY_CI_BOT_PRIVATE_KEY | Full contents of the .pem file |
Why register in both places?
GitHub Actions references different secret stores depending on what triggered the workflow. Workflows triggered by Dependabot run in a restricted security context for supply chain attack prevention and can only access Dependabot secrets. Meanwhile, workflows triggered by regular events (manual push, standard PRs, workflow_dispatch, etc.) reference Actions secrets. Therefore, to ensure the same workflow works correctly regardless of what triggered it, you need to register secrets in both locations.
The .pem file content looks like this. Copy the entire content as-is:
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA...
...
-----END RSA PRIVATE KEY-----
If you use the same App across multiple repositories, register the secrets in Organization Settings → Secrets to share them.
Step 5: Update the Workflow
Use the actions/create-github-app-token action to generate a token at runtime.
steps:
# 1. Checkout with GITHUB_TOKEN (read) — don't leave token in .git/config
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
persist-credentials: false
# 2. Install dependencies (no credential in .git/config at this point → safe)
- name: Setup node
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- name: Update lockfile
run: yarn install --no-immutable
# 3. Generate App token only right before push → minimize exposure time
- 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. Set remote URL with token and 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
The key points are:
actions/create-github-app-token@v1issues an installation token using the App ID and Private Key- This token is valid for approximately 1 hour and is newly issued for each workflow run
- Pushing with this token triggers other workflows normally
- The git commit author uses the
<app-name>[bot]format
Security: Minimizing Token Exposure
For security, simply replacing GITHUB_TOKEN with a GitHub App token is not enough. You also need to consider the time and scope of token exposure.
actions/checkout defaults to persist-credentials: true, which saves the provided token in .git/config’s extraheader. If you checkout with an App token and then run yarn install, a dependency’s post-install script could read .git/config and steal the token with write permissions.
Dependabot PRs update external packages, making them particularly vulnerable to this supply chain attack scenario.
[Dangerous configuration]
checkout (App token) → token saved in .git/config → yarn install → post-install script can steal the token
[Safe configuration]
checkout (persist-credentials: false) → no token in .git/config → yarn install (safe) → generate App token → push
To prevent this, the following configuration is recommended:
persist-credentials: false: Don’t save credentials in.git/configduring checkout- Move token generation to just before push: Generate the token after running external code like
yarn install - Use
git remote set-urlto set the token only for push: Configure the token only when it’s needed
A Note on Permissions
When switching to a GitHub App token, you should also review the job’s permissions block. permissions controls the GITHUB_TOKEN permissions, so if GITHUB_TOKEN is not used for pushing in the job, there’s no need to grant contents: write.
# Before: write needed because GITHUB_TOKEN handles push
permissions:
contents: write
# After: push is handled by GitHub App token, so read is sufficient
permissions:
contents: read
Following the principle of least privilege, avoid granting unnecessary permissions to tokens that are not actually used.
Testing
To verify that the GitHub App token works correctly, you can create the following test workflow:
name: Test GitHub App Token
on:
push:
branches:
- test/app-token # Only run on the test branch
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 Procedure
Add this test workflow to the test/app-token branch and push it. You can verify the setup through the following steps:
- Add the workflow file above to the
test/app-tokenbranch and push - Check the workflow execution results in the Actions tab
- Verify the setup based on each step’s success or failure:
- Generate token succeeds → Secrets are configured correctly
- Checkout succeeds → Token can access the repo
- Push succeeds → Token can push
- Delete the workflow file after testing is complete
Troubleshooting
| Error Message | Cause | Solution |
|---|---|---|
could not create workflow dispatch event: HTTP 404 | App is not installed on the repository | Check Install App in Step 3 |
The permissions requested are not granted to this installation | Insufficient App permissions | Add permissions in App Settings → Permissions |
Could not resolve to a valid ref | Incorrect Private Key | Verify the full .pem content is registered as a Secret |
Resource not accessible by integration | Installation scope issue | Verify the App is installed on the target repository |
Conclusion
The fact that pushing with GITHUB_TOKEN in GitHub Actions doesn’t trigger other workflows is an intentional design to prevent infinite recursion. There are two main ways to solve this:
| Method | Best For |
|---|---|
| PAT | When a quick temporary fix is needed |
| GitHub Apps | Recommended for production |
GitHub Apps have a higher initial setup barrier since Organization Owner permissions are required, but once configured, they provide secure, manageable token handling that isn’t tied to any individual. Especially for workflows that deal with external packages like Dependabot PRs, it’s recommended to also consider minimizing the token exposure scope.
References
- GitHub Docs: Triggering a workflow from a workflow
- GitHub Docs: Registering a GitHub App
- actions/create-github-app-token
Was my blog helpful? Please leave a comment at the bottom. it will be a great help to me!
App promotion
Deku.Deku created the applications with Flutter.If you have interested, please try to download them for free.