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.

Written by Jamie Tanna's profile image Jamie Tanna on , and last updated on .

Content for this article is shared under the terms of the Creative Commons Attribution Non Commercial Share Alike 4.0 International, and code is shared under the Apache License 2.0.

This blog post is 100% human-written, and has nothing derived from LLMs (be it code, editorial support or prose).

This post was filed under articles.

Interactions with this post

Interactions with this post

Below you can find the interactions that this page has had using WebMention.

Have you written a response to this post? Let me know the URL:

Do you not have a website set up with WebMention capabilities? You can use Comment Parade.