OIDC Authentication for GitHub Actions
Let GitHub Actions jobs reach Mergify with the ID token GitHub signs for them instead of a MERGIFY_TOKEN application key, under trust policies you write.
A GitHub Actions job can reach Mergify without MERGIFY_TOKEN. It asks GitHub for an OpenID
Connect (OIDC) ID token and trades it with Mergify for a token that works for one hour, on that
job’s repository only. Your trust policies decide which jobs get one. This page takes a workflow
that stores a ci application key in MERGIFY_TOKEN to one
that stores nothing.
A ci key never expires, works on every repository of your organization, and sits in repository
secrets, where it can leak from a build log or a workflow change. A token from an ID token covers
one repository for an hour, and Mergify reads the job’s branch from what GitHub signed rather than
from what the job reports.
How it works
Section titled How it worksWhen a Mergify client finds no MERGIFY_TOKEN in a job granted id-token: write, it exchanges
the job’s ID token:
-
The client asks GitHub for an ID token meant for the Mergify API,
https://api.mergify.com. GitHub signs into it what the job is: its repository, workflow, event, ref, commit and environment. -
Mergify checks the signature, that the token was meant for it, and that its GitHub App is installed on the repository. Then it looks for a trust policy of your organization that matches the job.
-
When one matches, Mergify answers with a token that does what a
cikey does, on that repository only: upload test results and scopes, read the test runs recorded for a commit and the quarantine list, and read the flaky-detection and test-selection data a test run fetches. Any other request is refused. The client renews the token before its hour is up. -
When none matches, the exchange is refused and the client prints why in the job log. The job then runs without a token: the test framework plugins run the tests with no quarantine and upload nothing,
mergify ci junit-processfails its step, andmergify ci scopes-sendwarns and sends no scopes.
A MERGIFY_TOKEN that is set always wins: the client exchanges only when the variable is unset or
empty. Until you remove the key from a job, nothing changes for it, so you can write your trust
policies first.
A pull_request job’s results are filed under the ref GitHub signed for it, its pull request’s
merge ref refs/pull/<number>/merge, and still count for that pull request.
Before you start
Section titled Before you start-
A client that exchanges. The latest releases of the test framework plugins (pytest, RSpec, Minitest, Vitest, Playwright, Jest, Mocha), of the Mergify CLI (
mergify ci junit-processandmergify ci scopes-send), and ofmergifyio/gha-mergify-ci. -
GitHub Actions. Buildkite, Jenkins and CircleCI jobs keep using a
cikey.
Move your workflows off MERGIFY_TOKEN
Section titled Move your workflows off MERGIFY_TOKENWrite a trust policy
Section titled Write a trust policyOpen Settings → Developer → OIDC on the dashboard.
GitHub organization Owners and users holding the CI Admin delegated
role can write policies.
Click Trust CI on Every Repository to create a policy named CI on every repository: every
repository, any workflow, the CI events, any environment. Click Write a Policy instead to
start narrower. Trust policies below describes each line.
Until your organization has a policy, every exchange is refused.
Let the job request an ID token
Section titled Let the job request an ID tokenGrant id-token: write to the job, or to the whole workflow:
permissions: id-token: write # lets the job ask GitHub for an ID token contents: read # actions/checkout needs itA permissions block sets every permission it does not list to none, so list the ones the
job’s other steps need as well. mergifyio/gha-mergify-ci and the test framework plugins need
nothing more.
A reusable workflow gets
at most what its caller grants: give id-token: write to the calling job. A job of the called
workflow that declares its own permissions must list id-token: write too.
Remove the key from the job
Section titled Remove the key from the jobRemove MERGIFY_TOKEN from the step’s env, the token input from mergifyio/gha-mergify-ci,
and --token from mergify commands. A job that runs pytest then needs nothing else:
jobs: test: runs-on: ubuntu-latest permissions: id-token: write contents: read steps: - uses: actions/checkout@v5 # Add your setup steps here (Python, Node.js, etc.) - name: Run tests run: pytestThe upload step of mergifyio/gha-mergify-ci keeps its other inputs:
- name: Mergify CI Upload # A pull request from a fork gets no ID token: skip the upload there if: (success() || failure()) && !github.event.pull_request.head.repo.fork uses: mergifyio/gha-mergify-ci@v30 with: report_path: junit.xml test_step_outcome: ${{ steps.tests.outcome }}Here tests is the id of your test step. The fork condition replaces the HAS_MERGIFY_TOKEN
guard of Pull requests without the
secret: once the secret is
deleted, that guard skips every upload. If your test step has continue-on-error: true, fail the
job when the tests failed and nothing was uploaded:
- name: Fail when tests failed and no report was uploaded if: steps.tests.outcome == 'failure' && github.event.pull_request.head.repo.fork run: exit 1A line that still reads ${{ secrets.MERGIFY_TOKEN }} once you delete the secret gives an empty
value, and an empty MERGIFY_TOKEN counts as unset. Stop passing the secret to reusable
workflows too: they need id-token: write from their caller, not the key.
Check the exchanges
Section titled Check the exchangesThe Exchanges tab of the OIDC settings lists every exchange for 90 days: the repository, the workflow, the event and ref, and the policy that allowed it or why it was refused. On an exchange refused because no trust policy matched, Allow Jobs Like This opens a new policy filled in with that job’s repository, workflow and event. Nothing is saved until you save it.
Exchanges lists the jobs refused because no trust policy matched, because Mergify’s GitHub App cannot access the repository, or because the token was minted for another Mergify. A token Mergify cannot verify is not listed: read the job’s log.
Workflows run for Dependabot pull requests get an ID token like the repository’s other runs, so
they no longer need MERGIFY_TOKEN in your Dependabot
secrets either.
Delete the ci keys
Section titled Delete the ci keysOnce your repositories show only allowed exchanges, delete the MERGIFY_TOKEN secrets, then the
ci keys under Settings → Developer → Application Keys. Keep one for the jobs that cannot
use OIDC. A key you do not delete keeps working.
Trust policies
Section titled Trust policiesA policy reads as a sentence: “Jobs from these repositories running these workflows on these events in these environments.” A job gets a token when one policy matches it, and a policy matches when all four lines do. Policies have no order and none denies: a narrow policy cannot hold back what a broad one allows.
-
Repositories: all repositories, the default, new ones included as Mergify’s GitHub App gets access to them; all except the ones you pick; or only the ones you pick.
-
Events: the CI events, the default (
push,pull_request,merge_group,scheduleandworkflow_dispatch); the events you pick; or any event. -
Workflows: any workflow, the default; or the workflow files of the job’s own repository, such as
.github/workflows/ci.yml, and reusable workflows, optionally pinned to a ref. -
Environments: any environment, or none, the default; or the environments you name, which a job in no environment does not match.
Keep these in mind while writing one:
-
pull_request_target,workflow_runandissue_commentare not CI events. They run with the repository’s identity on behalf of people who cannot push to it: add them only if those workflows never run code they were sent. -
A workflow file of the job’s own repository names the workflow the run started from. A reusable workflow it calls, even one of the same repository (
uses: ./.github/workflows/...), is added as a reusable workflow. -
A workflow file of the job’s own repository matches at any ref, so anyone who can push a branch can edit it. Only a reusable workflow pinned to a protected ref, or an environment whose protection rules GitHub checks before the job starts, keeps them from doing so.
-
A private repository can send write tokens to workflows run for pull requests from forks. Those runs then get an ID token, and a policy that allows
pull_requestgives the fork’s code a token for your repository. -
An organization can have up to 100 policies.
-
Deleting a policy does not revoke the tokens it already allowed: they expire within the hour.
Jobs that keep a key
Section titled Jobs that keep a key-
Pull requests from forks. GitHub gives their runs no ID token, as it gives them no secrets on a public repository, so they report nothing either way (see Pull requests without the secret). If your organization sends secrets to workflows run for pull requests from forks, keep the key for those runs only:
env: MERGIFY_TOKEN: ${{ github.event.pull_request.head.repo.fork && secrets.MERGIFY_TOKEN || '' }}Every other run gets an empty value and exchanges its ID token. Drop the fork condition from the upload step of
mergifyio/gha-mergify-cithen, so that fork runs upload with the key. -
pull_request_target,workflow_runandissue_commentjobs that send scopes. These jobs run on the base branch, and a token from their ID token reports scopes only for the commit the job runs on, never for the pull request’s head. Mergify refuses the upload, andmergify ci scopes-sendfails the step. Give those jobs acikey. -
Jobs outside GitHub Actions. Buildkite, Jenkins and CircleCI jobs use a
cikey.
Tests inside containers
Section titled Tests inside containerstox, docker run and Docker Compose start your tests without the job’s environment. Pass
ACTIONS_ID_TOKEN_REQUEST_URL and ACTIONS_ID_TOKEN_REQUEST_TOKEN through with the variables
the plugin reads:
GitHub sets them in a job granted id-token: write, and without them the plugin has no ID token
to exchange.
GitHub Enterprise Server
Section titled GitHub Enterprise ServerAn on-premise Mergify install connected to GitHub Enterprise Server accepts the ID
tokens of that server’s GitHub Actions, issued by
https://<your-github-host>/_services/token, and none from github.com. The clients request the
ID token for the Mergify API URL the job reaches, so set MERGIFY_API_URL (the
mergify_api_url input of mergifyio/gha-mergify-ci) to your install’s URL, as you do with a
key.
On github.com, Mergify also accepts the issuer GitHub Enterprise Cloud lets an enterprise switch
its Actions to, https://token.actions.githubusercontent.com/<enterprise>. Nothing to configure.
Troubleshooting
Section titled TroubleshootingWhen Mergify refuses an exchange, its reason appears in the job log. A test framework plugin prints it at the end of the run:
Mergify won't upload this run's test results: MERGIFY_TOKEN is not set, and thejob's ID token could not be exchanged for a Mergify token.Error: Mergify refused the job's ID token: <reason>The Mergify CLI, which mergifyio/gha-mergify-ci runs, ends its no Mergify credential found
error with the same reason; mergify ci scopes-send prints it as a warning. What to do depends on
how the reason starts:
-
<organization> has no trust policy: write a trust policy. -
No trust policy of <organization> matches this job: the reason names the job’s workflow, event and environment, and Exchanges shows which line of each policy refused it. Widen a policy, or use Allow Jobs Like This. -
Mergify's GitHub App has no access to <repository>: add the repository to the app’s installation in your GitHub organization settings. -
The ID token was issued for another audience: the job requested its ID token for another Mergify. SetMERGIFY_API_URLto the audience the reason names. -
This Mergify accepts the ID tokens of GitHub Actions jobs from <issuer> only: the job reached a Mergify that serves another GitHub. CheckMERGIFY_API_URL.
MERGIFY_TOKEN is not set, with nothing about an ID token. The client did not try an
exchange:
-
The job lacks
id-token: write. For a reusable workflow, check the calling job. -
The run is for a pull request from a fork.
-
The tests run in a container that was not passed
ACTIONS_ID_TOKEN_REQUEST_URLandACTIONS_ID_TOKEN_REQUEST_TOKEN. -
The plugin or CLI is not on its latest release.
The CLI prints no Mergify credential found instead. In a job with no ID token, the scopes upload
of mergifyio/gha-mergify-ci warns Mergify token is not set, scopes will not be sent to Mergify API.
Mergify could not exchange the job's ID token. Mergify could not be reached, or could not
fetch GitHub’s signing keys. The tests run, but this run’s results are not uploaded and no test is
quarantined in it. Run the job again.
could not get the job's ID token: HTTP <status>. GitHub did not sign an ID token for the
job, with the same effect. Run the job again.
This Mergify CI token was issued to a job that runs on <ref>. A token from a job’s ID token
tried to send scopes for a commit that job does not test. See Jobs that keep a
key.
Was this page helpful?
Thanks for your feedback!