Migrate from the github_actions Action to GitHub Actions Events
Step-by-step guide to replace the deprecated github_actions action with native GitHub Actions event triggers.
The github_actions action is deprecated
and will be removed in a future release. This guide walks you through
triggering the same workflows from GitHub Actions
events
instead, taking Mergify out of the dispatch path entirely.
What Changes
Section titled What ChangesToday, Mergify evaluates your rule’s conditions and calls GitHub’s
workflow_dispatch API to start your workflow. After the migration, your
workflow subscribes to the GitHub event it cares about and decides for itself
whether to run.
github_actions action | GitHub Actions events | |
|---|---|---|
| Who decides to run | Mergify, from .mergify.yml conditions | Your workflow, from on: filters and job if: |
| How it starts | workflow_dispatch API call | The event GitHub already emits |
| Passing pull request data | inputs rendered by Mergify | The github.event payload |
| Permissions needed | Mergify write permission on Actions | None beyond the workflow’s own |
| Limits | 10 workflows per rule, 10 inputs each | None |
Migration Steps
Section titled Migration Steps1. Turn the rule’s conditions into an event trigger
Section titled 1. Turn the rule’s conditions into an event triggerFind the rule that dispatches your workflow and read its conditions. Most map
directly onto an on: filter, a job-level if:, or both:
| Mergify condition | Event trigger | Job condition |
|---|---|---|
merged | pull_request / types: [closed] | github.event.pull_request.merged == true |
closed and -merged | pull_request / types: [closed] | github.event.pull_request.merged == false |
label=deploy | pull_request | contains(github.event.pull_request.labels.*.name, 'deploy') |
base=main | pull_request / branches: [main] | None |
author=octocat | pull_request | github.event.pull_request.user.login == 'octocat' |
-draft | pull_request / add ready_for_review to types: | github.event.pull_request.draft == false |
files~=^src/ | pull_request / paths: ['src/**'] | None |
Four of these need care.
paths: takes glob patterns, not the regular expressions files~= accepts, so
^src/ becomes src/**.
The -draft row needs an explicit types: because ready_for_review is not
one of the defaults, which are opened, synchronize and reopened. Listing
all four is what makes the workflow fire when someone clicks “Ready for
review”. Without it that click emits no run at all, and no job condition can
compensate for a run that was never created.
The label=deploy row reads the label off the pull request rather than off the
event, using contains(...labels.*.name...). The alternative,
github.event.label.name, exists only on the labeled activity type and is
null everywhere else. That matters as soon as you combine rows: a rule
conditioned on both merged and label=deploy needs types: [closed] from
the first row, where github.event.label does not exist. Reading the label off
github.event.pull_request composes with every activity type.
Rows are otherwise composable by intersecting the types: and ANDing the job
conditions, as long as every expression you AND is valid for the activity types
you kept.
Conditions that Mergify computes itself, such as check results or review counts, have no equivalent trigger. Step 3 covers those.
2. Replace the workflow inputs with the event payload
Section titled 2. Replace the workflow inputs with the event payloadEvery variable the github_actions action could render is already in the event
payload:
| Input variable | GitHub Actions expression |
|---|---|
{{ author }} | github.event.pull_request.user.login |
{{ number }} | github.event.pull_request.number |
{{ title }} | github.event.pull_request.title |
{{ base }} | github.event.pull_request.base.ref |
{{ head }} | github.event.pull_request.head.ref |
{{ repository_full_name }} | github.repository |
Read these expressions directly wherever the workflow used inputs.<name>.
These expressions come from the pull_request object, which the pull_request,
pull_request_target and pull_request_review payloads all carry. The events
in step 3 do not:
workflow_run, check_suite and status have no pull_request object, so
github.event.pull_request.number resolves to an empty string there rather
than failing. Under those triggers, look the pull request up through the API
instead.
Remove the workflow_dispatch trigger only once nothing else dispatches the
workflow. Another Mergify rule, a gh workflow run command or the “Run
workflow” button in the Actions tab all depend on it, and dispatching a
workflow that no longer declares the trigger fails.
3. Re-express the conditions that have no event
Section titled 3. Re-express the conditions that have no eventConditions that Mergify computes have no GitHub event that fires when they
become true. Check results and review counts can be re-expressed by subscribing
to the closest event and evaluating the state in the job. Conditions with no
observable event at all, such as conflict or any queue-state attribute, are
covered at the end of this step.
For check-success, when the check comes from a workflow in the same
repository, use workflow_run:
on: workflow_run: workflows: [CI] types: [completed]
permissions: contents: read
jobs: deploy: if: >- github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'main' && github.event.workflow_run.head_repository.full_name == github.repository runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - run: ./deploy.shThree details make this snippet longer than it looks like it should be.
workflows: matches the other workflow’s name:, not its filename and not the
check name. This is the one place the mapping from check-success is not
direct: check-success takes the name GitHub reports for the check, which for
GitHub Actions is the job name. A rule reading check-success=build, where
build is a job inside a workflow named CI, becomes workflows: [CI].
GitHub only starts a workflow_run workflow whose file is on the repository’s
default branch, and the run itself executes against the default branch:
GITHUB_REF and GITHUB_SHA point there, not at the commit the CI run
validated. The bare actions/checkout above therefore gets the default branch.
To act on the validated commit, pin ref: ${{ github.event.workflow_run.head_sha }},
and read GitHub’s guidance on script
injection
first.
The if: guards the branch and the head repository because workflow_run runs
in the base repository’s context with its secrets and a read/write token, and
it fires for CI runs on pull requests from forks too. Without those guards, a
green CI run on an unreviewed fork pull request would run ./deploy.sh with
your credentials.
For checks reported by a third-party CI, use the
check_suite
or
status
event instead. Both carry the same default-branch requirement as workflow_run:
a workflow file on a feature branch never receives them, so it cannot be tested
before it merges.
For review conditions, let GitHub compute the answer rather than counting
reviews yourself, since reviewDecision already accounts for dismissed and
superseded reviews:
on: pull_request_review: types: [submitted]
permissions: contents: read pull-requests: read
jobs: deploy: runs-on: ubuntu-latest steps: - name: Read the review decision id: review env: GH_REPO: ${{ github.repository }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | decision=$(gh pr view "${{ github.event.pull_request.number }}" \ --json reviewDecision --jq .reviewDecision) echo "decision=$decision" >> "$GITHUB_OUTPUT" - if: steps.review.outputs.decision == 'APPROVED' uses: actions/checkout@v5 - if: steps.review.outputs.decision == 'APPROVED' run: ./deploy.shThe job reads the decision into an output and skips the remaining steps instead
of failing on anything other than an approval. A run: ... || exit 1 gate would
turn every COMMENTED review, every CHANGES_REQUESTED review and every
approval before the last required one into a failed run, leaving a red check-run
on the pull request until the final approval lands.
The permissions: block is required: gh pr view reads reviewDecision
through the GraphQL API, which a default token restricted to contents: read
cannot query.
reviewDecision reflects the approval count your branch protection or ruleset
requires, and is empty when no rule requires a review at all, so this step only
works on a branch that has one. For a threshold that differs from it, or for a
branch with no review requirement, list the reviews through
the reviews
API
and count them yourself.
pull_request_review carries the same fork restriction as pull_request, and
there is no pull_request_review_target to swap in. On a pull request from a
fork this job gets no repository secrets, so gate deployments that need them on
the merge instead.
Some conditions have no event and no reliable state to poll. conflict is one:
GitHub computes mergeability asynchronously, so github.event.pull_request.mergeable
is null on the first delivery and a job gated on it flaps. Queue-state
attributes such as queue-position are another, since they describe Mergify’s
own state and are not visible to GitHub at all. A rule that depends on these is
not a candidate for this migration. Move the workflow’s trigger to something
observable, or keep the rule until the workflow no longer needs that condition.
4. Remove the rule from your Mergify configuration
Section titled 4. Remove the rule from your Mergify configurationBefore deleting anything, walk through Behavior Differences to Check: a workflow started by an event does not always run in the same context as a dispatched one.
Once the workflow triggers on its own, delete the github_actions action from
.mergify.yml. If the rule existed only to dispatch, delete the whole rule.
Mergify’s write permission on Actions is part of the Mergify GitHub App and is not granted per action, so there is nothing to revoke once the rule is gone.
Complete Before & After Example
Section titled Complete Before & After ExampleA rule that transitions a Jira issue and posts to Slack once a pull request is
merged into main.
Before: a Mergify rule dispatching two workflows.
pull_request_rules: - name: Run post-merge automation conditions: - merged - base=main actions: github_actions: workflow: dispatch: - workflow: jira-transition.yaml inputs: pull_request: "{{ number }}" author: "{{ author }}" - workflow: slack-notify.yaml inputs: title: "{{ title }}"After: each workflow triggering on the merge itself.
name: Jira transition
on: pull_request: types: [closed] branches: [main]
jobs: transition: if: github.event.pull_request.merged == true runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - run: ./scripts/jira-transition.sh env: JIRA_TOKEN: ${{ secrets.JIRA_TOKEN }} PULL_REQUEST: ${{ github.event.pull_request.number }} AUTHOR: ${{ github.event.pull_request.user.login }}name: Slack notification
on: pull_request: types: [closed] branches: [main]
jobs: notify: if: github.event.pull_request.merged == true runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - run: ./scripts/slack-notify.sh env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} TITLE: ${{ github.event.pull_request.title }}The Mergify rule disappears: both workflows now react to the same merge Mergify was watching for.
Behavior Differences to Check
Section titled Behavior Differences to CheckFour differences catch people out. Check each against your own workflows before deleting the rule.
Secrets on pull requests from forks. Mergify dispatched your workflow with
the repository’s own credentials, so secrets were always available. A workflow
triggered by pull_request from a fork gets a read-only GITHUB_TOKEN and no
secrets, so a step that posts to Slack or calls Jira will now fail. This holds
for every activity type, closed included, and for pull_request_review as
well. When your repository takes pull requests from forks, swap pull_request
for
pull_request_target
and keep the same types: and branches: filters: it carries the same payload
and runs with the repository’s secrets. Two caveats. It runs with full
permissions and must never check out the pull request’s code. And its workflow
file is read from the base repository’s default branch, not from the pull
request’s base branch, so adding it only to a release branch produces no runs
at all.
Which version of the workflow file runs. A pull_request run uses the
workflow file from the pull request’s merge commit, so a contributor’s changes
to the workflow take effect on their own pull request. Dispatches ran your
repository’s default branch instead, or the branch named by the rule’s ref
option. A rule that set ref was pinning both the workflow and the code it
runs to a vetted branch, and no event trigger reproduces that: switch such a
workflow to on: push against that branch, or check out the pinned ref
explicitly rather than relying on the event’s own.
How often it fires. A Mergify rule matches while its conditions hold; an
event fires each time it happens. A workflow on types: [labeled] runs again
on every subsequent label. Add a
concurrency
group when repeated runs would collide.
Where failures show up. A failed dispatch surfaced in the Mergify check-run on the pull request. Once migrated, failures appear in the Actions tab and in the workflow’s own check-run instead.
Was this page helpful?
Thanks for your feedback!