In the modern continuous integration and continuous delivery (CI/CD) landscape, efficient resource management is paramount. As developers frequently push updates to repositories, GitHub Actions workflows are triggered repeatedly. Without proper intervention, these overlapping executions can consume significant compute resources, increase costs, and delay feedback loops. The standard approach to managing this issue has evolved from relying on third-party custom actions to utilizing native GitHub features. Understanding the mechanics of canceling previous runs—whether through native concurrency groups or specialized third-party actions—is essential for optimizing workflow efficiency and maintaining a responsive development environment.
The Native Concurrency Approach
The most recommended and robust method for handling duplicate or redundant workflow runs is the native concurrency property introduced by GitHub. This feature allows developers to define groups of concurrent workflows, ensuring that only one run within a specific group proceeds while others are canceled or queued. This approach is preferred over third-party actions because it is integrated directly into the GitHub Actions runner infrastructure, offering better reliability and lower overhead.
The configuration is straightforward and relies on two key parameters: group and cancel-in-progress. The group parameter defines the unique identifier for the concurrency group, often combining the workflow name and the current branch reference. The cancel-in-progress parameter determines whether ongoing runs in that group should be terminated when a new run is triggered.
yaml
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
In this configuration, workflows are grouped by their name and the specific branch or reference they are running on. The cancel-in-progress logic is conditional: it cancels previous runs only if the current run is not on the main branch. This strategy protects critical production deployments on the main branch from being interrupted by newer pushes, while allowing rapid cancellation of in-progress runs on feature or development branches to ensure the latest code is always tested first. Developers are advised to consult GitHub's official documentation for detailed implementation guidelines, as this native method effectively replaces the need for most custom cancellation actions.
Third-Party Cancellation Actions
Before the widespread adoption of native concurrency groups, or in scenarios requiring more granular control, third-party GitHub Actions were developed to handle the cancellation of previous runs. These actions operate by querying the GitHub API to identify and terminate ongoing or queued workflows. While still useful in specific edge cases, they require careful configuration and understanding of their limitations compared to native solutions.
Styfle Cancel Workflow Action
One of the prominent third-party solutions is the styfle/cancel-workflow-action. This action is designed to cancel any previous runs for a given workflow that are not yet completed. Specifically, it targets runs with a status of queued or in_progress. The action works by capturing the current branch and SHA (commit hash) upon a git push. It then queries the GitHub Workflow Runs API to find previous runs that match the branch but differ in SHA, effectively identifying older runs that should be terminated in favor of the latest code.
To implement this, the action is typically added as the first step in a workflow job. This positioning ensures that as soon as the job starts, it checks for and cancels any conflicting previous runs.
yaml
name: 'Usage of cancel-workflow-action GitHub Action'
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action
The action offers additional parameters for fine-grained control. For instance, the only_status parameter allows users to restrict cancellation to specific states, such as waiting. This is particularly useful when workflows are paused awaiting manual approval for deployment to protected environments. By setting only_status: 'waiting', the action will only interrupt workflows that are idle and waiting for approval, leaving actively running builds untouched.
Another critical parameter is force_cancel. By default, GitHub Actions may preserve certain runs based on conditions like always() or approval states. Setting force_cancel: true bypasses these conditions, ensuring the run is terminated regardless of its current state or configuration constraints.
yaml
name: Cancel
on: [push]
jobs:
cancel:
name: 'Cancel Previous Runs'
runs-on: ubuntu-latest
timeout-minutes: 3
steps:
- uses: styfle/cancel-workflow-action
with:
only_status: 'waiting'
Regarding permissions, the action requires write access to the actions scope. By default, GitHub provides the GITHUB_TOKEN with sufficient read/write permissions for this task. However, in environments with restrictive global permissions, it is best practice to explicitly grant the required permissions at the job level. This limits the scope of elevated permissions to only the job that needs them.
yaml
jobs:
test:
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action
with:
access_token: ${{ github.token }}
Pierreraffa Cancel Previous Runs Action
Another viable option is the pierreraffa/cancel-previous-runs-action. This action supports cancellation for pull_request, push, and merge_group events. It requires GitHub-hosted runners with gh (GitHub CLI) and jq (JSON processor) tools, along with write permissions configured in the repository settings.
For pull_request and push events, the action cancels previous runs that are in the in_progress or queued state, originating from the same workflow or a specified list of workflows, and belonging to the same branch. For merge_group events, it targets runs related to the same Pull Request. This is particularly relevant when managing merge queues, where a failure in an early entry might necessitate the cancellation and re-evaluation of subsequent entries to prevent redundant execution.
yaml
jobs:
cancel-previous-runs:
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: pierreraffa/[email protected]
This action is designed to be placed at the beginning of the workflow. Once executed, it scans for all runs related to the current context and cancels those that are older than the current run.
GongT Cancel Previous Workflows Action
The GongT/cancel-previous-workflows action focuses on canceling all previous runs for the current workflow on the current branch. It offers an optional DELETE parameter that, when set to true, removes all previous runs, including those that have already completed. This can be useful for cleaning up historical run data, though it should be used with caution.
yaml
name: Cancel
on: push
jobs:
cancel:
name: Cancel Previous Runs
runs-on: ubuntu-latest
steps:
- name: cancel running workflows
uses: GongT/cancel-previous-workflows@master
env:
GITHUB_TOKEN: ${{ github.token }}
DELETE: true
The action relies on environment variables such as GITHUB_TOKEN and GITHUB_REPOSITORY for authentication and context. It also supports advanced command-line operations for deleting logs and old runs, though these are typically managed through the action's primary cancellation function in standard workflows.
Advanced Scenarios and Limitations
While third-party actions provide flexibility, they come with specific limitations that developers must consider. A significant constraint arises when dealing with Pull Requests originating from forked repositories. In such cases, the GITHUB_TOKEN provided to the workflow has read-only permissions for security reasons. Since canceling a workflow requires write permissions, these actions will fail to cancel runs in forked PR contexts.
To mitigate this, a hybrid approach is often recommended. Developers can use a scheduled workflow triggered by a cron job to periodically scan and cancel duplicate runs across all branches and PRs. This scheduled workflow operates with full permissions and can clean up stale runs that the primary workflow cannot address due to permission restrictions. This method serves as a fallback or complementary strategy to the early-step cancellation approach, ensuring comprehensive coverage of resource optimization.
Furthermore, it is important to note that many of these third-party actions, including styfle/cancel-workflow-action and GongT/cancel-previous-workflows, are not certified by GitHub. They are provided by third-party developers and are governed by their own terms of service, privacy policies, and support documentation. Users should evaluate the security and maintenance status of these actions before integrating them into critical production workflows.
Conclusion
The management of GitHub Actions workflow runs has matured from reliance on custom scripts to a sophisticated ecosystem of native and third-party tools. The native concurrency group feature represents the current best practice for most scenarios, offering a clean, efficient, and secure way to handle duplicate runs. However, third-party actions like styfle/cancel-workflow-action remain valuable for specialized use cases, such as forcing cancellation of waiting approvals or managing complex merge queue scenarios. Understanding the interplay between these tools, their permission requirements, and their limitations allows teams to build resilient, cost-effective, and responsive CI/CD pipelines. As GitHub continues to enhance its native capabilities, the trend will likely continue toward reduced dependency on external actions, but for now, a nuanced understanding of all available options is essential for the modern DevOps engineer.