Checking out the branch from a fork in GitHub Actions, when using pull_request_target

This week, I've setting up a GitHub Actions Workflow which will:
- Look at modified file(s) on the PR
- Look up the owner of the files (based on an external HTTP API, using a GitHub Actions Secret)
- Comment back onto the PR to indicate that ownership
- On a private repo (that will never need to run on a public repo)
This looked something like:
on:
pull_request:
branches: [main]
paths:
- configuration/tools/*
jobs:
list-changed-files:
# omitted for brevity
annotate-tool-changes:
runs-on: ubuntu-latest
needs: list-changed-files
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Determine PR comments + make them
env:
ALL_CHANGED_FILES: ${{ needs.list-changed-files.outputs.changed-files }}
run: |
set -o pipefail
echo "Checking if there are changes in $ALL_CHANGED_FILES"
# note that we don't glob on purpose
.scripts/do-the-thing $ALL_CHANGED_FILES
I had it all working when pushing branches, but as soon as folks started trying to push PRs from forks, the Workflow could no longer access the base repo's secrets - for very good secure defaults!
In particular this meant that the HTTP API secrets weren't available, which led to the Workflow not knowing the owners, and failing gracefully, but with a lot less useful information.
However in this case, I did want those secrets to be available to the forked Workflow.
The "best" option here is to use the pull_request_target
event, which then makes these secrets available.
β οΈ Using the pull_request_target
can be dangerous. Moreso with public repos, but still has implications for private repos.
β οΈ This is something that should be done at your own risk, taking in mind the risks. Please do your own research.
To do so, I made the following change:
on:
- pull_request:
+ pull_request_target:
branches: [main]
paths:
- configuration/tools/*
However, this wasn't quite enough. From here, we'd end up seeing errors in actions/checkout
:
...
remote: Repository not found.
Error: fatal: repository 'https://github.com/{...}/{...}/' not found
Error: The process '/usr/bin/git' failed with exit code 128
Looking at an actions/checkout issue, various web + AI searches, it looked like it would be unfortunately very painful to check out the branch, as GITHUB_TOKEN
doesn't seem to have access to the forked repo.
I was losing hope of it ever working, when my excellent colleague Victor Martinez suggested trying gh pr checkout
, using the GITHUB_TOKEN
.
I'd originally dismissed this, given using the GITHUB_TOKEN
to do a git fetch
failed, but it turns out it does work π
This resulted in the following setup:
on:
pull_request:
branches: [main]
paths:
- configuration/tools/*
jobs:
list-changed-files:
# omitted for brevity
annotate-tool-changes:
runs-on: ubuntu-latest
needs: list-changed-files
permissions:
contents: read
pull-requests: write
steps:
# i.e. this allows us to use `origin/HEAD` to compare against
- name: Check out base repository
uses: actions/checkout@v4
- name: Fetch PR branch from fork
run: |
gh pr checkout ${{ github.event.number }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Determine PR comments + make them
env:
ALL_CHANGED_FILES: ${{ needs.list-changed-files.outputs.changed-files }}
run: |
set -o pipefail
echo "Checking if there are changes in $ALL_CHANGED_FILES"
# note that we don't glob on purpose
.scripts/do-the-thing $ALL_CHANGED_FILES
From here, you are now checked out on the fork's branch, and have access to i.e. origin/HEAD
or origin/main
, to i.e. diff against.