Github Actions and Heroku Pipelines

Lately I’ve started moving projects away from CircleCI and just to use Github Actions natively within our repos. There’s absolutely nothing wrong with CircleCI! But, it’s still one more service to configure and pay for when we’re already paying for Github Actions along with our organizational account. So in an effort to tidy up a bit, we’ve switched over our CI pipelines to use Actions instead.

The process was mostly smooth, but that’s a post for another day. One of the key areas that wasn’t smooth was showing the build status on our Heroku Pipeline. We use Heroku’s Pipeline feature to deploy quickly between environments and keep things in sync between our development, staging, and production servers. CircleCI automatically showed status updates in Heroku and they look something like this:

A green check mark next to the deployed commit on Heroku Pipelines
Green check next to the deployed app commit hash

We rely on those green checks to make sure that we’re not accidentally deploying a build that didn’t pass all of our checks. In a normal pipeline, you may prefer to not allow builds to deploy until the checks pass. The first step of our pipeline is a development server, and I don’t care if the build doesn’t pass there. It also speeds up our deployment by deploying to dev while the tests run.

But with Github Actions by default, here’s what the deployed commits looked like:

A deployed commit without any indication of build status
A deployed app with no CI status checks

It took some digging, and I didn’t find much out there in my searches to find out how this works. It turns out that these checks are called Commit Statuses in Github, and there’s an API for working with them.

Luckily for us, Github Actions provides easy access to the Github API with the Github CLI. Here are the steps needed within a Github Actions workflow to get those checks back:

On Begin Test Suite

When our test suite begins, we want to set up a “pending” commit status to let Github and Heroku Pipelines know that something is in progress. Here’s the workflow code to enable the first step:

steps:
  - name: Checkout code
    uses: actions/checkout@v4

  - name: Announce Pending Status
    run: |
      gh api \
        --method POST \
        -H "Accept: application/vnd.github+json" \
        -H "X-GitHub-Api-Version: 2022-11-28" \
        /repos/${GITHUB_REPOSITORY}/statuses/${GITHUB_SHA} \
        -f state='pending' \
        -f target_url="https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" \
        -f description='Build has started ...' \
        -f context='Github Actions'      
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

And the resulting visual from Heroku:

A yellow dot indicating a commit that is being tested
A commit that is currently pending test suite

A few bits about the step above:

  1. The target_url parameter allows us to put any URL into the status. When the Heroku Pipeline status is clicked, it will direct your browser to the run on Github Actions. This can easily be set to something else if you prefer.
  2. Don’t forget to include the env for GH_TOKEN. This allows the GITHUB_TOKEN secret to be accessible within the api call. (This secret is set automatically by Github, and does not need to be configured.)
  3. The description parameter is the string that will be visible in the Heroku UI.
  4. The context parameter should be a unique key for each check you are running. We are just using one here, but you could easily create more than one context for multiple jobs. (Linting, test suite, etc could all be their own steps) When updating status later, you just need to pass the same context again.
  5. Finally, the state parameter is set to “pending” here to denote the yellow indicator and that something is “in progress”.

A note on permissions: If your Github organization or account doesn’t have admin permissions, you’ll probably want to add the following to the top of your job declaration:

build:
  name: "My Job Name"
  runs-on: ubuntu-latest
  permissions:
    actions: read
    checks: write
    contents: read
    pull-requests: read
    statuses: write

On Test Suite Passing

When the test suite completes without error, we want a green check on Heroku. The same Github CLI call can be modified to show success like so:

steps:
  # Previous steps here, put this one at the end:

  - name: Announce Success Status
    if: ${{ success() }}
    run: |
      gh api \
        --method POST \
        -H "Accept: application/vnd.github+json" \
        -H "X-GitHub-Api-Version: 2022-11-28" \
        /repos/${GITHUB_REPOSITORY}/statuses/${GITHUB_SHA} \
        -f state='success' \
        -f target_url="https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" \
        -f description='Github Actions Passed!' \
        -f context='Github Actions'      

    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Here is what that update looks like on Heroku now:

A green check indicating a successful test build
A completed and successful test

Notice in this step we included the line if: ${{ success() }}. This ensures that the CLI call in this step only runs if the tests passed.

On Test Suite Failure

If the test suite fails with error, we want to note this as well. Here’s the step call to update the commit status with a failure:

steps:
  - name: Announce Failure Status
    if: ${{ failure() }}
    run: |
      gh api \
        --method POST \
        -H "Accept: application/vnd.github+json" \
        -H "X-GitHub-Api-Version: 2022-11-28" \
        /repos/${GITHUB_REPOSITORY}/statuses/${GITHUB_SHA} \
        -f state='failure' \
        -f target_url="https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" \
        -f description='Github Actions Failed :(' \
        -f context='Github Actions'

      ./bin/ci_notify --type=failure      

    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

And here is the resulting visual from Heroku for a failed suite:

A red X indicating this build has failed
A failed build

Just like the passing check, we’re using if: ${{ failure() }} to ensure this step is called only if a previous step fails.


That’s it, and we’re all set! Now each of our Heroku Pipeline steps have a proper build status indicator from Github Actions.

Last updated 3/9/2024. If you find this helpful, please let me know!.