My release workflow had one job: when a v* tag lands, build the package and publish it. My version-bump workflow had one job too: bump the version, commit, tag, push.
Both worked. I tested each one by hand. Then I wired them together, went to bed, and woke up to a shiny new tag on GitHub and zero releases. No failed run. No red X. The release workflow simply never started.
Nothing was broken. GitHub Actions was doing exactly what it's documented to do: GITHUB_TOKEN doesn't trigger workflows. Anything your workflow pushes, tags or opens using the default token is invisible to every other workflow's on: trigger.
This bites almost everyone who automates releases, formatting commits, changelog updates or bot PRs. Here's the mechanic, a reproduction, and the fixes ranked by how much I like them.
TL;DR
- Events created with the built-in
GITHUB_TOKEN(push, tag, PR opened, comment, release) do not start new workflow runs. The only exceptions areworkflow_dispatchandrepository_dispatch. - GitHub does this on purpose, to stop a workflow from triggering itself forever.
-
actions/checkoutwritesGITHUB_TOKENinto the repo's git config, so a latergit pushuses it even if you set a PAT in some env var. - Fixes: push with a GitHub App token, explicitly call
gh workflow run, use a reusable workflow viaworkflow_call, or use a PAT as a last resort. - Bot-opened PRs with required checks will sit at "Expected — Waiting for status to be reported" forever for the same reason.
Why doesn't GITHUB_TOKEN trigger workflows?
GITHUB_TOKEN doesn't trigger workflows because GitHub blocks it to prevent recursive runs. If a push workflow could push with its own token and fire itself again, one commit would become an infinite loop that burns runner minutes until someone notices.
So GitHub draws a hard line. When an event is caused by the GITHUB_TOKEN of a workflow run, it gets recorded (the commit exists, the tag exists, the PR exists), but no workflow is created in response. The two exceptions are workflow_dispatch and repository_dispatch, because those are explicit "please run this" requests, not side effects.
The painful part is the silence. There is no skipped run, no warning in the logs, no annotation. The Actions tab just doesn't show anything, which looks identical to "my on: filter is wrong." I spent an embarrassing amount of time rewriting a perfectly good tag pattern.
How do I reproduce the GITHUB_TOKEN trigger problem?
Two files. The first tags and pushes using the default token:
# .github/workflows/bump.yml
name: bump
on: workflow_dispatch
permissions:
contents: write
jobs:
bump:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
echo "bump $GITHUB_RUN_NUMBER" >> CHANGELOG.md
git commit -am "chore: bump"
TAG="v0.0.$GITHUB_RUN_NUMBER"
git tag "$TAG"
git push origin HEAD "$TAG"
The second listens for tags:
# .github/workflows/release.yml
name: release
on:
push:
tags: ["v*"]
jobs:
release:
runs-on: ubuntu-latest
steps:
- run: echo "Releasing ${{ github.ref_name }}"
Run bump from the Actions tab. The commit lands. The tag lands. release never runs. Now push a tag from your laptop with git push origin v9.9.9 and release fires instantly. Same trigger, different token, different result.
Why doesn't my PAT fix it? (the checkout trap)
Your PAT probably isn't being used. actions/checkout defaults to persist-credentials: true, which stores the token it checked out with in the local git config as an auth header. Every git push after that authenticates as GITHUB_TOKEN.
This is the trap I fell into on attempt two. I did this:
- uses: actions/checkout@v4
- run: git push origin HEAD "$TAG"
env:
GH_TOKEN: ${{ secrets.RELEASE_PAT }}
GH_TOKEN only affects the gh CLI. Plain git ignores it and happily uses the credentials checkout already wrote. Result: still no release.
The token has to go into checkout itself:
- uses: actions/checkout@v4
with:
token: ${{ secrets.RELEASE_PAT }}
Once you get that right, the push counts as "a person or app did this" and downstream workflows fire.
What is the best fix for GITHUB_TOKEN not triggering workflows?
For most teams, the best fix is a GitHub App installation token. It triggers workflows like a human push, isn't tied to one employee's account, has scoped permissions, and expires on its own after about an hour.
Create a GitHub App in your org, give it Contents: read & write (plus Pull requests if it opens PRs), install it on the repo, and store its ID and private key. Then:
steps:
- uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ vars.RELEASE_APP_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
- uses: actions/checkout@v4
with:
token: ${{ steps.app-token.outputs.token }}
- run: |
git tag "v0.0.$GITHUB_RUN_NUMBER"
git push origin "v0.0.$GITHUB_RUN_NUMBER"
Order matters. The token step must run before checkout, or checkout persists GITHUB_TOKEN and you're back in the trap.
Can I trigger the workflow without a PAT or an App?
Yes. Since workflow_dispatch is one of the two exceptions, you can skip the implicit trigger and call the downstream workflow explicitly with the default token. You need actions: write.
permissions:
contents: write
actions: write
steps:
# ...tag and push as before...
- run: gh workflow run release.yml --ref "v0.0.$GITHUB_RUN_NUMBER"
env:
GH_TOKEN: ${{ github.token }}
And add workflow_dispatch to the release workflow's triggers:
on:
push:
tags: ["v*"]
workflow_dispatch:
Running it with --ref set to the tag means github.ref inside the release run is refs/tags/v0.0.N, so tag-based logic keeps working.
The other no-secret option: if both workflows live in the same repo, don't use an event at all. Make release a reusable workflow with on: workflow_call and call it as a job:
jobs:
bump:
# ...
release:
needs: bump
uses: ./.github/workflows/release.yml
secrets: inherit
Honestly, this is the cleanest version for a single repo. No event, no token question, and the whole pipeline shows up as one run graph. One catch: the called workflow inherits the caller's context, so github.ref is still the branch, not your new tag. Pass the tag in as a workflow_call input.
Why is my bot's pull request stuck on "Waiting for status to be reported"?
Because the PR was opened or updated with GITHUB_TOKEN, so the pull_request workflows that produce your required checks never ran. Branch protection is waiting for a status that will never arrive.
You see this with dependency-bump bots, auto-formatters, and anything using gh pr create with the default token. Auto-merge sits there politely forever.
Fixes, in order of effort:
- Quick unblock: a human closes and reopens the PR, or pushes an empty commit to the branch. That event comes from a person, so checks run.
-
Real fix: open the PR with a GitHub App token (same pattern as above, pass it as
GH_TOKENtogh pr createand astokento checkout). - Avoid: marking the check as not required. That fixes the symptom and removes the safety net.
Won't a PAT or App token cause infinite loops?
It can, because you just removed the protection GitHub added. If your push-triggered workflow pushes with an App token, it will trigger itself.
Two guards I use:
jobs:
format:
if: github.actor != 'my-release-app[bot]'
App tokens act as <app-slug>[bot], so filtering on the actor stops the self-trigger cleanly. The other option is putting [skip ci] in the bot's commit message, which skips push and pull_request workflows for that commit. That one is blunt: it skips everything, including the checks you might actually want on that commit.
A few related sharp edges
-
Workflow files:
GITHUB_TOKENcan't push changes to.github/workflows/. The push gets rejected for missingworkflowspermission. An App or PAT with workflow scope is required. - PATs belong to people. When that person leaves, releases stop. If you must use one, make it a fine-grained PAT scoped to the single repo.
- Release-please, semantic-release and friends hit this exact issue when they create tags or releases with the default token. Their docs point you at a custom token for this reason.
Checklist: debugging "my workflow didn't run"
Before rewriting your on: filter for the fourth time:
- Who created the event? Check the commit, tag or PR author.
github-actions[bot]is the smoking gun. - Did
actions/checkoutget a customtoken:? If not,git pushusedGITHUB_TOKEN. - Did the App token step run before checkout?
- Is the downstream trigger
workflow_dispatchorrepository_dispatch? Those are the only ones the default token can fire.
So why doesn't GITHUB_TOKEN trigger workflows?
GITHUB_TOKEN doesn't trigger workflows because GitHub Actions deliberately ignores events created by the default token (pushes, tags, pull requests, releases) to prevent recursive workflow runs; only workflow_dispatch and repository_dispatch are exempt. The event still happens, but no workflow starts, and nothing is logged. To chain workflows, push with a GitHub App installation token passed to actions/checkout before it runs, call the next workflow explicitly with gh workflow run and actions: write, or use a reusable workflow via workflow_call. Then add an actor check so the new token doesn't create the loop GitHub was protecting you from.
Written by the developer behind Preterview, an interview prep platform.
Top comments (0)