Exploring Reusing Workflows in GitHub

June 30, 2025#Tools
Article
Author image.

Sarah Dutkiewicz, Senior Trainer

When we’re on client engagements, we bring our expertise, and we also learn from our clients. One project has exposed us to the concept of reusing GitHub workflows. One of the things we commonly use in our repos is code coverage, and we post the code coverage output to a PR. So in this post, I’m going to talk through how we moved our Comment on PR to reusable workflow.

Code Coverage for eShopOnNServiceBus in PR #20

The Original Comment on PR Workflow

I’ve blogged about this process in the past in our post on Using workflow_run in GitHub Actions. In htat post, we talked about why comment-on-pr is a separate workflow.

This was the original comment-on-pr.yml GitHub Actions workflow that we started with:

name: Comment on the Pull Request

# read-write repo token
# See: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
on:
  workflow_run:
    workflows: [".NET Core"]
    types:
      - completed

jobs:
  comment:
    runs-on: ubuntu-latest

    # Only comment on the PR if this is a PR event
    if: github.event.workflow_run.event == 'pull_request'

    steps:
      - name: Get the PR Number artifact
        uses: dawidd6/action-download-artifact@v2
        with:
          workflow: ${{ github.event.workflow_run.workflow_id }}
          workflow_conclusion: ""
          name: pr-number
      - name: Read PR Number into GitHub environment variables
        run: echo "PR_NUMBER=$(cat pr-number.txt)" >> $GITHUB_ENV
      - name: Confirm the PR Number (Debugging)
        run: echo $PR_NUMBER
      - name: Get the code coverage results file
        uses: dawidd6/action-download-artifact@v2
        with:
          workflow: ${{ github.event.workflow_run.workflow_id }}
          workflow_conclusion: ""
          name: code-coverage-results
      - name: Add Coverage PR Comment
        uses: marocchino/sticky-pull-request-comment@v2
        with:
          number: ${{ env.PR_NUMBER }}
          recreate: true
          path: code-coverage-results.md

We used this in eShopOnWeb and also in its NServiceBus-demo copy known as eShopOnNServiceBus. Since we may have other code repos that could use this, we moved it to our new workflows repo and reference it in our other workflows.

Refactoring the Workflow for Reusability

When I first evaluated how we use the workflow, I realized that there are high dependencies between our build workflow’s artifacts and the comment-on-pr workflow. So I identified what pieces we needed to pass in:

  • PR Number - which PR gets the comment
  • Code Coverage artifact name - as one of the plugins requires the artifact name used in the build workflow
  • Code Coverage artifact path - used with the comment plugin

Once I identified these parts, I could extract them as workflow inputs.

We changed the top on statement from workflow_run - being dependent on a caller workflow by name - to workflow_call, which is preferred for reusable workflows. In the workflow_call section, we can define the inputs to this workflow that get passed in a with block in the caller workflow.

This is what the reusable comment-on-pr workflow looks like:

name: Comment on the Pull Request

on:
  workflow_call:
    inputs:
      pr-number:
        required: true
        type: string
      code-coverage-artifact-name:
        required: true
        type: string
      code-coverage-artifact-path:
        required: true
        type: string

jobs:
  comment:
    runs-on: ubuntu-latest

    # Only comment on the PR if this is a PR event
    if: ${{ github.event_name }} == 'pull_request'

    steps:   
    - name: Get the code coverage results file
      uses: dawidd6/action-download-artifact@v6
      with:
        workflow: ${{ github.event.workflow_run.workflow_id }}
        workflow_conclusion: ""
        name: ${{ inputs.code-coverage-artifact-name }}

    - name: Add Coverage PR Comment
      uses: marocchino/sticky-pull-request-comment@v2
      with:
        number: ${{ inputs.pr-number }}
        recreate: true
        path: ${{ inputs.code-coverage-artifact-path }}

Now that the inputs are determined, let’s look at calling this from another repo. Reusable workflows are used within jobs, so this is the job we would use:

   add-code-coverage-to-pr:     
     uses: NimblePros/NimblePros.GitHub.Workflows/.github/workflows/comment-on-pr.yml@main
     with:
       pr-number: YOUR_PR_NUMBER
       code-coverage-artifact-name: YOUR_CODE_COVERAGE_ARTIFACT_NAME
       code-coverage-artifact-path: YOUR_CODE_COVERAGE_ARTIFACT_PATH

But It Isn’t Necessarily That Straightforward

Passing some variables around shouldn’t be hard. It looks straightforward, right? 🤔

Unfortunately, when implemented this in eShopOnNServiceBus, I realized that it’s a bit more complicated. This is what the workflow looks like:

Pass PR Number, artifact name, and artifact path
Pass PR Number, artifact name, and artifact path
Build Workflow
Writeable Comment-on-PR
Reusable Comment-on-PR

Let me address the questions you might be wondering.

Why do you have to have the middle workflow?

As I mentioned in the first blog post, comment-on-pr needs to separate reads and writes. I wrote that blog post awhile ago, so I might have forgotten about it. So I had this job working in the the dotnetcore.yml workflow. But I separated it out again.

Why do you create files for the artifacts?

I create files for the artifacts because that is how I learned to copy details from one workflow to another workflow.

Why do you use another artifact downloader?

I use dawidd6/action-download-artifact because it allows us to get the artifacts directly from a specific workflow. It’s a GitHub Action that allows us to get artifacts from a workflow for specific criteria.

Why do you have 2 jobs in the middle Comment on PR?

One of the jobs gets the artifacts from the previous workflow. Then, it reads in the values and stores them in output variables for the job, to be passed to the job that calls the reusable workflow. The reusable workflow can be called at the job level. I also make use of needs in the second job, so that they execute sequentially.

Conclusion

Now that I’ve implemented this once, I can call the reusable workflow from another repo. Seeing how reusable workflows work, I look forward to identifying other processes that we consistently use and try to remove some of the duplication where it makes sense. If you find yourself using the same workflows over and over, consider - on a case-by-case basis - whether reusing workflows makes sense for you!


Copyright © 2025 NimblePros - All Rights Reserved