Orchestrating Execution Pauses and Approval Gates in GitHub Actions

The ability to halt, pause, or conditionally delay the execution of a Continuous Integration and Continuous Deployment (CI/CD) pipeline is a critical requirement for enterprise-grade software delivery. In a standard automated environment, workflows typically trigger and execute to completion without interruption. However, real-world deployment scenarios often necessitate human intervention—whether for quality assurance sign-offs, security audits, or coordinated release windows. In the GitHub Actions ecosystem, "pausing" a workflow can refer to three distinct operational states: the administrative disabling of a workflow to prevent triggers, the implementation of manual approval gates to control promotion between environments, and the synchronization of jobs via polling for external check completions. Understanding these mechanisms is essential for preventing catastrophic deployment failures, optimizing resource consumption, and maintaining strict governance over production environments.

Administrative Workflow Disabling

GitHub provides a native mechanism to temporarily disable a workflow without necessitating the removal of the YAML configuration file from the repository. This functionality is accessible via both the GitHub User Interface (UI) and the REST API, providing a non-destructive way to halt automation.

The impact of this feature is most significant when an automated process begins to exhibit unstable behavior. For instance, if a workflow contains a bug that generates a flood of incorrect requests to an external API or service, the resulting "denial of service" effect can lead to IP banning or service degradation. By disabling the workflow, an operator can instantly stop the bleeding without modifying the source code, allowing time for a fix to be developed and tested.

Furthermore, this administrative pause is vital for cost management. In environments where GitHub Actions minutes are a finite resource, a non-critical workflow that consumes excessive compute time can be paused to prioritize higher-value jobs. This is particularly relevant for organizations operating on a budget or using private repositories with limited monthly minute allocations.

The utility of this feature extends to fork management. When a developer forks a repository, they inherit all the workflows of the original project. If the original project includes resource-heavy scheduled workflows (cron jobs) that are unnecessary for the fork's specific purpose, the developer can disable them to avoid wasting resources while maintaining the integrity of the original project's structure. Similarly, if an external service dependency is down, disabling the workflow prevents a backlog of failed runs that would otherwise clutter the Actions tab and obscure genuine failures.

Manual Approval Gates via Marketplace Actions

While GitHub offers native environment-based approvals, these are restricted to GitHub Enterprise for private repositories. To circumvent this limitation, the community utilizes specialized actions, such as trstringer/manual-approval, which allow for the creation of approval gates in any repository type, including private ones.

The operational logic of a manual approval action involves the creation of a GitHub Issue. The workflow reaches the approval step and triggers the action, which then opens a new issue in the repository. This issue serves as the "notification" and "input" mechanism for the approvers. The workflow then enters a polling state, waiting for specific keywords in the issue comments to either proceed or terminate.

The specific keywords recognized by this system are:

  • Approval keywords: "approve", "approved", "lgtm", "yes"
  • Denied keywords: "deny", "denied", "no"

These keywords are case-insensitive and can be followed by optional punctuation, such as a period or an exclamation mark. If all designated approvers provide an approval keyword, the workflow resumes. If any single approver provides a denied keyword, the workflow is immediately failed. Upon resolution, the action closes the GitHub issue automatically.

The configuration for such a gate requires specific permissions and inputs. For the action to create and manage the approval issue, the workflow must be granted explicit write permissions for issues.

yaml permissions: issues: write

A detailed implementation of a manual approval step involves defining the approvers and the content of the issue:

yaml steps: - uses: trstringer/manual-approval@v1 with: secret: ${{ github.TOKEN }} approvers: user1,user2,org-team1 minimum-approvals: 1 issue-title: "Deploying v1.3.5 to prod from staging" issue-body: "Please approve or deny the deployment of version"

Since version 1.10.0, the behavior regarding issue content has been refined. The issue-title is now applied exactly as provided without additional wrapping strings. The issue-body and issue-body-file-path are no longer set as the primary description of the issue; instead, they are added as comments to the issue. This ensures that the primary issue remains a clean record of the approval request while the detailed context is preserved in the comment thread.

Technical Constraints and Cost Implications of Paused Jobs

Implementing a pause via a marketplace action is not without technical and financial costs. Unlike a native "suspended" state, a job waiting for approval is still considered "running" by the GitHub runner.

The following table outlines the critical constraints associated with paused workflows:

Constraint Limit/Impact Consequence
Concurrent Job Allocation Occupies 1 slot Reduces the number of other jobs that can run simultaneously
Compute Cost Active VM/Instance User continues to be billed for minutes while the job is paused
Job Timeout 6 hours The job will fail automatically if not approved within 6 hours
Workflow Timeout 35 days The entire workflow fails if the total duration exceeds 35 days
Token Expiration 1 hour GitHub App tokens expire every 60 minutes; approvals beyond this may fail

Because a paused job continues to consume a virtual machine, it is highly recommended to use the timeout-minutes parameter. This prevents a workflow from hanging indefinitely and consuming all available minutes if an approver is unavailable.

The timeout-minutes can be applied in two ways:

Approach 1: As an input to the action:

yaml jobs: approval: steps: - uses: trstringer/manual-approval@v1 timeout-minutes: 60

Approach 2: As a job-level configuration:

yaml jobs: approval: timeout-minutes: 10 steps: - uses: trstringer/manual-approval@v1

Inter-Workflow Synchronization with Wait-on-Check

Another form of pausing is the "dependency pause," where a workflow must wait for a separate job or workflow to complete successfully before proceeding. This is achieved through the lewagon/wait-on-check-action, which leverages the GitHub Checks API to poll for specific results.

This mechanism is essential for complex microservices architectures where a deployment workflow in one repository must wait for a testing workflow in another repository to pass. The action can be configured to wait for a specific check name, a regular expression of checks, or a specific workflow name.

Example configurations for different waiting scenarios:

Waiting for a specific workflow name:

yaml - name: Wait for publish uses: lewagon/[email protected] with: ref: ${{ github.ref }} running-workflow-name: "Publish the package" repo-token: ${{ secrets.GITHUB_TOKEN }}

Waiting for a group of tests using a regular expression:

yaml - name: Wait for all test jobs uses: lewagon/[email protected] with: ref: ${{ github.sha }} check-regexp: "test-.*" repo-token: ${{ secrets.GITHUB_TOKEN }}

Waiting for a specific check while allowing certain conclusions:

yaml - name: Wait for checks (allow cancelled) uses: lewagon/[email protected] with: ref: ${{ github.ref }} check-name: "Run tests" repo-token: ${{ secrets.GITHUB_TOKEN }} allowed-conclusions: success,skipped,cancelled

In cases where certain checks are optional (such as linting or coverage reports) and should not block the pipeline, the ignore-checks parameter can be used. Furthermore, if a check is conditional and might not run at all, the fail-on-no-checks parameter should be set to false. By default, if no checks match the filter, the action fails with the message The requested check was never run against this ref, exiting....

Advanced Architectural Alternatives: Repository Dispatch

For users who require more flexibility than the manual-approval action provides—specifically those who want to avoid the cost of a running VM during a pause—the "Repository Dispatch" pattern is the superior architectural choice.

In this model, the workflow does not technically "pause" in a running state. Instead, it terminates its first phase by creating a GitHub issue or a comment requesting approval. The workflow then finishes and exits. A second, separate workflow is configured to trigger on issue_comment events. When a user comments /approve or /reject, this second workflow evaluates the user's permissions and the comment content. If the criteria are met, it uses a repository_dispatch event to trigger the final deployment workflow.

This approach provides several advantages:

  • Zero cost during the waiting period: No VM is running while waiting for human input.
  • Custom logic: Approvals can be routed to different paths based on the comment content.
  • Decoupled execution: The deployment phase only starts when the trigger is received, avoiding the 6-hour job timeout.

Compatibility and Runner Requirements

When implementing manual approval gates via the trstringer/manual-approval action, it is critical to ensure runner compatibility. The action is designed specifically for Linux environments.

Supported runners:

  • Linux/amd64: 64-bit Intel/AMD (x86_64)
  • Linux/arm64: 64-bit ARM (Apple M1)
  • Linux/arm/v8: 64-bit ARM

Unsupported runners:

  • Windows/amd64: Windows systems are not currently supported.
  • Any non-Linux runner architecture.

Detailed Analysis of Implementation Strategies

The choice of how to pause a GitHub Actions workflow depends entirely on the specific objective: administrative control, quality gating, or synchronization.

Administrative disabling is a coarse-grained tool. It is used for emergency shutdowns or cost-saving measures. It does not allow for "resuming" from where a workflow left off; it simply prevents future executions.

The manual-approval action provides a medium-grained tool. It allows a workflow to stop at a specific step and wait for a human. However, the "hidden cost" is the active compute time. Because the job is technically still running, the user is paying for the VM while the approver is sleeping or in another meeting. This makes it suitable for short-term approvals (minutes to a few hours) but unsuitable for long-term sign-offs (days).

The wait-on-check action is a technical synchronization tool. It is not designed for human decision-making but for ensuring that automated dependencies are satisfied. This is the most efficient way to link disparate workflows.

The Repository Dispatch method is the most sophisticated. By transforming a "pause" into a "split-workflow" architecture, it eliminates the financial and temporal constraints of the GitHub runner. This is the recommended path for Enterprise-level pipelines where approval cycles can take days and the cost of running an idle VM is unacceptable.

Sources

  1. GitHub Blog: Ability to disable actions workflows
  2. GitHub Marketplace: Manual Workflow Approval
  3. GitHub Marketplace: Wait on Check
  4. Latenode Community: How to pause GitHub Actions workflow for manual user approval

Related Posts