Share your failures with robots: surfacing activities with GitHub actions

Chad Carlson
Chad Carlson
Manager, Developer Relations
22 Dec 2021

In our previous announcement, we announced that activities on were now shareable. If you need to send a member of our support team something fishy going on in your build hook, or get help from another member of your team about a database migration taking longer than expected, you can send them to the exact logline you need help with.

But like I said before, having the ability to link to a line of logs - if the format of that link is consistent (which it is) - means we can surface it wherever and whenever it’s useful.

Let’s take a minute to look at the link we can copy for an activity:

So how can we leverage this new predictable link in our workflow? The first thing that comes to my mind is surfacing this link in my GitHub integrations.

Say I have a repository on GitHub, that’s been integrated to a project. My default branch is my production environment, and I’ve set up the standard integration defaults otherwise: environments are created automatically for pull requests. All of my team’s review takes place on these pull requests, and our checks on GitHub fail should the deployment for environment pr-X fail.

When they fail, we have the same round-a-bout investigation path I described in the previous post to diagnose where things broke: go to the project, go to the environment, find the activity, view the log, find the line where things went wrong.

But since I can share a log for an activity directly, and since I know what the structure of that link is going to look like, I should be able to remove that added work by quite a bit. Here’s what I want:

  • A pull request on GitHub provisions a development environment on
  • Each commit on the pull request results in a commit on that development environment.
  • I need successful deployments to be a deciding factor on whether or not that PR is successful, not merging if the deployment fails.
  • I want to know whether a deployment has failed.
  • If a deployment has failed, I’d like the shareable activity link to be surfaced directly on GitHub where my team is reviewing, so that they can quickly investigate what happened.

The first two pieces are taken care of by our GitHub integration already, and the third item is something you’re likely already enforcing with your repository’s settings. The last two is what we’ll focus on here, and that’ll mean writing some tools that respond to deployments to trigger some other workflow that can surface our activity link when it fails.

Whatever method we choose, to “build” the activity link we need to be able to retrieve the following from each deployment:

  • That and when an environment’s deployment has failed
  • The user/organization that the project belongs to
  • The project’s ID
  • The environment ID
  • The failed activity ID

Let’s get into it.

Waiting to deploy: Environment status via GitHub actions

If you’re not already familiar with GitHub actions, they are a method for including some configuration in YAML that can be used to trigger tests and other workflow steps in response to events that take place on your repository.

You can, for example, ensure that every commit pushed to your Python library can be used on a number of Python versions your users may have on their computers, or you can automatically publish your Node.js library to npm each time a new tag is pushed to the default branch. connects pushed commits to deployments, usually within the scope of a pull request, and the GitHub integration will add a status check to your pull requests that reflect the status of that deployment.

Each time you push to a pull request on GitHub that has been integrated with, a webhook is triggered to let know that it should sync with the repository and deploy the new commit. After this has occurred, will respond back to GitHub the environment’s status, which becomes available at repos/:repos/status/:ref. It will begin with a status of pending as the environment deploys, but once finished another message from will update GitHub of the successful deployment:

  "state": "success",
  "statuses": [
      "state": "success",
      "description": " Environment deployed",
      "target_url": "",
      "context": "platformsh",


The environment has deployed successfully, and sends along the generated environment URL in the target_url attribute - which allows you to click the Details link on the check on a pull request and see the deployed environment.

Success GitHub Status

If the environment fails to deploy, however, it will look more like this:

  "state": "failure",
  "statuses": [
      "state": "failure",
      "description": " Environment deployment failed",
      "target_url": "",
      "context": "platformsh",


Exactly the same, just with a different state message.

Failure GitHub Status

The target URL isn’t updated based on the failed deployment, so our Details link will simply take us to the environment URL which has failed to deploy. The failed URL doesn’t help us much here, but it does seem like the opportune place to insert our activity link. So, from here we have a clear goal: detect the failed deployment and update the target_url attribute with our failed activity link.

We start by creating some space for our workflows

mkdir .github
mkdir .github/workflows
touch .github/workflows/post-deploy.yaml

In a .github/workflows/post-deploy.yaml file we can start building out the action. In the first lines, we define when this action should occur. For now, we can simply say that it should run on every push to all branches except our default branch - we’ll add some logic later on to make sure this only runs on pull requests. After that, we can expose a CLI token and a GitHub token as environment variables to be used by the action.

name: Post-deploy

      - 'main'

    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

You can add your CLI token as a repository or organization level secret.

Now it’s time to define a few jobs. The first one is simply meant to determine:

  • Are we working from a pull request?
  • What is the URL (target_url) of the environment associated with the pull request?
  • What’s the status of the deployment?

Below is the snippet for the job build we’ll use to do this.

        runs-on: ubuntu-latest
        name: 'Get environment URL'
            commit_status: ${{ steps.status.outputs.env_status }}
            env_url: ${{ steps.url.outputs.env_url }}  
            integration_status: ${{ steps.wait.outputs.integration }}  
            - uses: actions/checkout@v2
            - name: 'Await deployment'
              id: wait
              run: |
                sleep 10
                STATUSES=$(curl -s$GITHUB_REPOSITORY/statuses/$GITHUB_SHA  | jq -r 'length')
                if [ $STATUSES == 0 ]; then
                    echo "Not on a integrated environment. Skipping."
                    echo "::set-output name=integration::none"
                    until [ "$COMMIT_STATUS" == "success" ] || [ "$COMMIT_STATUS" == "failure" ]; do
                      sleep 30
                      ENV_URL=$(curl -s$GITHUB_REPOSITORY/statuses/$GITHUB_SHA  | jq -r '.[0].target_url')
                      COMMIT_STATUS=$(curl -s$GITHUB_REPOSITORY/statuses/$GITHUB_SHA  | jq -r '.[0].state')
                      echo "Waiting for environment to deploy ...."
                      echo "  - $GITHUB_SHA"
                      echo "  - $COMMIT_STATUS"
                      echo "  - $ENV_URL"
                    echo "Environment deployed. Finished."
                    echo "::set-output name=integration::platformsh"
            - name: 'Pass status'
              id: status
              if: steps.wait.outputs.integration == 'platformsh'
              run: |
                COMMIT_STATUS=$(curl -s$GITHUB_REPOSITORY/statuses/$GITHUB_SHA  | jq -r '.[0].state')
                echo "::set-output name=env_status::$COMMIT_STATUS"
            - name: 'Pass URL'
              id: url
              if: steps.wait.outputs.integration == 'platformsh'
              run: |
                ENV_URL=$(curl -s$GITHUB_REPOSITORY/statuses/$GITHUB_SHA  | jq -r '.[0].target_url')
                echo "::set-output name=env_url::$ENV_URL"

In summary, we are:

  • Placing a request on the Status API.
  • Verifying that this is associated with an environment on (deploying from a Pull request).
  • Waiting for the status to update from pending to success or failure. In the case above, we’re literally placing requests on the status and waiting for it to change. This is unlikely to be the most efficient way of doing this, but it works - so ship it.
  • Passing that status and target_url value out of job build, so that those values will be accessible from subsequent jobs.

Now that the environment has deployed, we can pipe in these outputs to build the activity link in a new job called displaylogs:

        name: 'Display logs on failed deploy'
        runs-on: ubuntu-latest
        needs: build
        if: == 'failure'
            - name: 'Install the CLI'
              run: |    
                  # Install CLI
                  curl -sS | php
            - name: 'Retrieve the logs'
              id: activity
              run: |
                  # Get data.
                  IFS='-' read -ra my_array <<< "${{ }}"
                  IFS="." read -ra my_array <<< "${my_array[3]}"
                  ACTIVITY=$(~/.platformsh/bin/platform project:curl -p $PROJECT /environments/$ENVIRONMENT/activities | jq -r '.[0].id')
                  echo "      * PROJECT:     $PROJECT"
                  echo "      * ENVIRONMENT: $ENVIRONMENT"
                  echo "      * ACTIVITY:    $ACTIVITY"
                  echo "      * LOGS:$PROJECT/$ENVIRONMENT/log/$ACTIVITY"
                  echo "::set-output name=log_url::$LOG_URL"
            - name: 'Update integration target_url'
              run: |
                curl -s --location --request POST --header "Authorization: Bearer $GITHUB_TOKEN" \
                    --header 'Content-Type: application/json' \
                    --data-raw '{"state": "failure", "target_url": "${{ steps.activity.outputs.log_url }}", "context": "platformsh", "description": " Failed to deploy environment. View the activity log details."}' \

You’ll notice that job displaylogs needs the job build - it will only run after build has completed, and it will be able to access its outputs from the namespace It’s from this that we can restrict displaylogs to make our update to only those cases when the environment deployment has failed. After this check, the job

  • Installs the CLI.
  • Uses the target_url to retrieve our project ID, and environment name for the pull request.
  • Places a request on the projects/environments/:environment/activities endpoint on the API to get the most recent failed activity (and its ID).
  • Builds the activity log URL from the project ID, environment ID, and activity ID at
  • Places a final request onto the GitHub Status API, which updates the target_url value from to our activity URL.

With these changes in place, any failed deployment pushed to GitHub will be updated in this way. A successful deployment will provide the environment’s URL in the Details link of the integration status as usual, but anything that fails will take our team directly to the failed logs so that we can investigate what went wrong.

You can find the Gist of the GitHub action described above here.

Let us know where you’d like to see shareable activities by joining the Slack, and share with us how you’ve used it in your own workflow on our Community.