DEV Community

jidonglab
jidonglab

Posted on

GitHub Actions GITHUB_TOKEN Doesn't Trigger Workflows: 11 Tags, 0 Runs

A user opened an issue on one of my side projects: "Is this package abandoned? The README says 1.4.6 but npm says 1.3.0."

I checked GitHub. The tags were there: v1.3.1 through v1.4.6, eleven of them across nineteen days, each cut by my release workflow. I checked npm. Still 1.3.0. Then I checked the Actions tab for the publish workflow that was supposed to fire on every tag.

It had not run once. It hadn't failed or been skipped. There were simply no runs at all.

The cause is a GitHub Actions rule that is documented, deliberate, and still catches people every week: GITHUB_TOKEN doesn't trigger workflows. Anything your workflow does with the default token (push a commit, push a tag, open a PR, publish a release) will not start another workflow run.

TL;DR

  • Events created with the default GITHUB_TOKEN do not start new workflow runs. The only exceptions are workflow_dispatch and repository_dispatch.
  • This covers pushes, tags, pull requests, releases and comments made by github-actions[bot].
  • GitHub does this on purpose, to stop workflows from triggering themselves in an infinite loop.
  • Fixes: push with a GitHub App token or a PAT, call gh workflow run explicitly, or turn the second workflow into a reusable workflow called by the first.
  • If you switch tokens, pass the new token to actions/checkout. Otherwise git push keeps using GITHUB_TOKEN and nothing changes.

What did my broken release setup look like?

Two workflows. The first one I ran by hand when I wanted a release:

# .github/workflows/release.yml
name: release
on:
  workflow_dispatch:

permissions:
  contents: write

jobs:
  tag:
    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"
          npm version patch
          git push --follow-tags
Enter fullscreen mode Exit fullscreen mode

The second one publishes whenever a version tag shows up:

# .github/workflows/publish.yml
name: publish
on:
  push:
    tags: ["v*"]

jobs:
  npm:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          registry-url: https://registry.npmjs.org
      - run: npm ci && npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Enter fullscreen mode Exit fullscreen mode

Each half works on its own. When I tagged locally and pushed from my laptop, publish ran perfectly. That's how the first few releases went out, and it's why I never suspected anything when I moved tagging into CI.

Once the tag push came from inside a workflow, publish went quiet. There was no error or warning anywhere, and the run list stayed empty.

Why doesn't GITHUB_TOKEN trigger workflows?

GITHUB_TOKEN doesn't trigger workflows because GitHub blocks recursion at the token level. Any event caused by the repository's automatic GITHUB_TOKEN is not allowed to create a new workflow run, except workflow_dispatch and repository_dispatch.

Imagine a workflow on push that formats code and pushes the result. If that push triggered workflows, it would trigger itself, format again, push again, and burn your minutes forever. GitHub's answer is blunt: the built-in token can change your repo, but those changes are invisible to the trigger system.

The rule is broader than "pushes," which is where people get bitten twice. All of these are silent when done with GITHUB_TOKEN:

Action done with GITHUB_TOKEN Workflow that won't run
git push to a branch on: push
git push a tag on: push: tags
Open a PR (e.g. an auto-update bot) on: pull_request CI checks
Create a release via API or gh release create on: release
Comment on an issue on: issue_comment

The PR case is the nastiest one. A dependency-update or codegen workflow opens a PR with GITHUB_TOKEN, your required checks never start, and the PR sits forever at "Expected — Waiting for status to be reported." It looks like CI is broken, when really CI was never asked to run.

How do I tell if this is my problem?

Look for the absence of a run, not a failed run. If this rule is your problem, the downstream workflow has no entry at all for that commit or tag.

Three quick checks:

  1. Open the tag or commit on GitHub. If it was pushed by github-actions[bot], suspect this rule first.
  2. Open the downstream workflow in the Actions tab. Zero runs for that ref means it was never triggered. A skipped run would show up as skipped, so a missing one points at the token.
  3. Push the same kind of tag from your laptop. If the workflow runs instantly, your trigger config is fine and the token is the problem.

That last test took me 30 seconds and would have saved nineteen days.

How do I make a workflow trigger another workflow?

You have four options. I'll go from the one I now use to the one I'd only use in a pinch.

Fix 1: Merge them with a reusable workflow

Often the cleanest fix skips the second trigger entirely. Make publish.yml callable:

# publish.yml
on:
  push:
    tags: ["v*"]
  workflow_call:
Enter fullscreen mode Exit fullscreen mode

Then call it as a job from release.yml:

jobs:
  tag:
    # ...same as before...
  publish:
    needs: tag
    uses: ./.github/workflows/publish.yml
    secrets: inherit
Enter fullscreen mode Exit fullscreen mode

Nothing has to be "triggered," since publishing is just the next job. One catch: the called workflow checks out the ref of the caller, not the new tag, so check out the tag explicitly if your build reads the version from git.

Fix 2: Dispatch it explicitly

workflow_dispatch is one of the two exceptions, so GITHUB_TOKEN is allowed to start it on purpose:

permissions:
  contents: write
  actions: write

steps:
  # ...after git push --follow-tags...
  - run: gh workflow run publish.yml --ref "v$(node -p 'require(\"./package.json\").version')"
    env:
      GH_TOKEN: ${{ github.token }}
Enter fullscreen mode Exit fullscreen mode

Add workflow_dispatch: to the on: block of publish.yml, or this call fails. And don't forget actions: write, which the default permission set often doesn't include.

Fix 3: Push with a GitHub App token

If you really need the push itself to count as a normal event (other people's workflows, PR checks, release triggers), push with a token that isn't GITHUB_TOKEN. A GitHub App installation token is the best option. It isn't tied to a person, it expires on its own, and commits show up as your-app[bot].

steps:
  - uses: actions/create-github-app-token@v1
    id: app
    with:
      app-id: ${{ vars.RELEASE_APP_ID }}
      private-key: ${{ secrets.RELEASE_APP_KEY }}

  - uses: actions/checkout@v4
    with:
      token: ${{ steps.app.outputs.token }}

  - run: |
      npm version patch
      git push --follow-tags
Enter fullscreen mode Exit fullscreen mode

This is the setup that finally published 1.4.7. It also published nothing else, because I had already burned eleven version numbers.

Fix 4: Push with a fine-grained PAT

Same idea, less setup: create a fine-grained personal access token with Contents: read and write on that one repo, store it as a secret, pass it to checkout. It works. The downsides are that it acts as you, it expires on a date you'll forget, and it breaks when you leave the team.

Why did switching tokens still not work for me?

The first time I tried Fix 3, I generated the App token, exported it as an env var, and ran git push. Nothing triggered.

The reason is actions/checkout. By default it saves the token it used into the repo's local git config (persist-credentials: true), and later git commands in the job authenticate with that saved credential. If checkout ran with the default token, your git push goes out as GITHUB_TOKEN no matter what other secrets sit in the environment.

The fix is the line in the example above: pass the new token to checkout via token:. Checkout has to authenticate with the token you want the push to use.

Won't a real token bring back the infinite loop?

Yes. Once you push with an App token or PAT, you've opted out of GitHub's loop protection, so you need your own guard. If a push-triggered workflow commits and pushes with a real token, it will trigger itself.

Two guards that work:

  • Put [skip ci] in the bot's commit message. GitHub skips push and pull_request workflows for commits containing it.
  • Add a job condition such as if: github.actor != 'my-release-app[bot]'.

My release workflow runs on workflow_dispatch, so it can't loop. But the moment you combine "on push" with "push with a PAT," write the guard first.

What I changed

  • release.yml now uses a GitHub App token, passed to actions/checkout.
  • publish.yml also has workflow_dispatch, so I can re-run a missed tag by hand with gh workflow run publish.yml --ref v1.4.7.
  • A scheduled job compares the latest git tag with the npm latest version and fails loudly if they differ. That check would have caught this on day one, not day nineteen.

Does GITHUB_TOKEN trigger workflows?

No. In GitHub Actions, GITHUB_TOKEN doesn't trigger workflows: pushes, tags, pull requests, releases and comments created with the default GITHUB_TOKEN never start new workflow runs, except for workflow_dispatch and repository_dispatch events. GitHub does this to prevent recursive workflow loops. To chain workflows, call the second one as a reusable workflow, start it explicitly with gh workflow run, or push using a GitHub App installation token or a fine-grained PAT that you also pass to actions/checkout, and add a loop guard such as [skip ci] when the triggering event is push.


Written by the developer behind Preterview, an interview prep platform.

Top comments (0)