Orchestrating Human Oversight in GitHub Actions: Manual Triggers and Approval Gateways

The automation paradigm of Continuous Integration and Continuous Deployment (CI/CD) prioritizes speed and consistency, yet critical deployment pipelines often require human intervention to prevent catastrophic failures or unauthorized changes. GitHub Actions has evolved to support these hybrid workflows through two distinct mechanisms: the native workflow_dispatch event, which allows users to manually initiate workflows, and third-party solutions like the manual-approval action, which introduces mandatory approval gates within existing workflows. Understanding the technical implementation, security implications, and operational constraints of these tools is essential for engineering robust, secure, and flexible deployment architectures.

Native Manual Triggers via workflow_dispatch

GitHub introduced the workflow_dispatch event to address a specific operational need: the ability to run workflows on demand without triggering them through code commits or pull request events. Historically, running a workflow required pushing a commit, which cluttered the repository history with meaningless changes solely intended to trigger a job. With workflow_dispatch, developers gain precise control over when a workflow executes, isolating manual runs from the automated commit history.

When a workflow is configured with the workflow_dispatch trigger, the GitHub Actions interface displays a "Run workflow" button on the Actions tab. This button serves as the primary interface for initiating the workflow. A key feature of this mechanism is branch selection; users can choose which branch the workflow will execute against, allowing for testing or deployment of specific versions without merging code.

Beyond simple initiation, workflow_dispatch supports the injection of custom inputs. These inputs are defined in the workflow configuration using the same syntax as action inputs, creating a dynamic form in the GitHub user interface. When a user triggers the workflow, they are presented with fields corresponding to these inputs, allowing them to pass configuration parameters directly into the run. These values are accessible within the workflow via the github.event context, specifically under github.event.inputs.

For example, a workflow might require a log level or a set of test tags to be specified at runtime. The configuration defines these inputs with descriptions, required flags, and default values. During execution, steps within the workflow can reference these inputs to adjust behavior dynamically. This capability transforms static workflows into interactive tools, suitable for scenarios such as running Lighthouse audits, deploying to specific environments, or executing ad-hoc debugging jobs.

```yaml
on:
workflow_dispatch:
inputs:
logLevel:
description: 'Log level'
required: true
default: 'warning'
tags:
description: 'Test scenario tags'

jobs:
printInputs:
runs-on: ubuntu-latest
steps:
- run: |
echo "Log level: ${{ github.event.inputs.logLevel }}"
echo "Tags: ${{ github.event.inputs.tags }}"
```

This native functionality is available to all GitHub repositories, providing a foundational layer of manual control that does not require third-party dependencies or enterprise licensing.

Third-Party Manual Approval Gates

While workflow_dispatch allows users to start a workflow manually, it does not inherently provide a mechanism to pause an automated workflow for human review before proceeding. This gap is critical for deployment pipelines where an automated build and test process completes successfully, but a final human sign-off is required before pushing changes to production. GitHub’s native solution for this involves the use of "Environments," which allow for required reviewers. However, this functionality has historically been restricted or required GitHub Enterprise for private repositories, limiting its accessibility for many teams.

The manual-approval action, developed by third-party contributor trstringer, fills this gap by providing a manual approval gate that works on private repositories without requiring GitHub Enterprise. This action operates by creating a GitHub issue in the repository where the workflow is running. The issue is assigned to the designated approvers. The workflow then pauses, waiting for responses in the form of comments on that issue.

The approval logic is keyword-based. The action monitors the issue for specific comments from the assigned approvers. Approval keywords include "approve", "approved", "lgtm", and "yes". Denial keywords include "deny", "denied", and "no". These keywords are case-insensitive and can include optional punctuation such as periods or exclamation marks. If all required approvers respond with an approval keyword, the workflow continues to the next step. If any approver responds with a denial keyword, the workflow exits with a failed status. Once the decision is reached, the action automatically closes the issue.

yaml - 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 v1.3.5."

This approach democratizes the approval workflow feature, making it accessible to open-source projects and private repositories on standard GitHub plans. However, it introduces dependencies on third-party code and specific operational constraints that must be carefully managed.

Technical Constraints and Runner Compatibility

The manual-approval action is not universally compatible with all runner environments. It relies on specific underlying technologies that limit its execution to Linux-based runners. Specifically, it supports 64-bit Intel/AMD (x86_64), 64-bit ARM (Apple M1), and 64-bit ARM (arm/v8) architectures. It is explicitly unsupported on Windows runners or any non-Linux architecture. Attempting to run this action on a windows-latest runner will result in failure, necessitating the use of ubuntu-latest or other Linux-based runners for workflows requiring manual approval.

Furthermore, the action is subject to the broader timeout limitations of GitHub Actions. A workflow has a maximum runtime of 35 days. Since the approval gate pauses the workflow until a human response is received, the duration of the approval process counts toward this 35-day limit. Teams must consider response time expectations and potential costs associated with long-running jobs. While the action previously included a timeout-minutes input, this was removed as it did not function as intended, leaving the 35-day workflow limit as the only hard stop.

Starting from version 1.10.0, the behavior of the approval issue content changed. Previously, the issue body might have been formatted with predefined strings. Now, the issue title is exactly what is provided in the configuration, and the body or body file content is added as comments rather than the main issue description. This change provides cleaner issue tracking and more flexibility in how the approval request is presented to the team.

Security Considerations and Organization Teams

Integrating manual approval into a workflow introduces security considerations, particularly regarding authentication and authorization. The action requires a secret token to create and manage the GitHub issue. In many cases, the default GITHUB_TOKEN provided by GitHub Actions is sufficient. However, when using organization teams as approvers, additional complexity arises.

The default GITHUB_TOKEN does not have the necessary permissions to list the members of an organization team. Consequently, if a team (e.g., org-team1) is specified in the approvers list, the action cannot identify the individual members to assign the issue. To resolve this, teams must generate a custom token using a GitHub App with specific permissions. This requires creating an Organization GitHub App with read-only access to organization members and the Issue: Read & Write role.

The process involves generating a private key for the GitHub App and storing the App ID and private key as repository secrets. A separate action, actions/create-github-app-token, is used to generate a token at runtime. This token is then passed to the manual-approval action.

```yaml
jobs:
myjob:
runs-on: ubuntu-latest
steps:
- name: Generate token
id: generatetoken
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.APP
ID }}
private-key: ${{ secrets.GHPRIVATEKEY }}

  - name: Wait for approval
    uses: trstringer/manual-approval@v1
    with:
      secret: ${{ steps.generate_token.outputs.token }}
      approvers: myteam
      minimum-approvals: 1

```

A critical constraint of using GitHub App tokens is their expiration time. These tokens expire after 60 minutes. If the approval process takes longer than one hour, the token will become invalid, and the job will fail due to bad credentials. Teams must ensure that approvers are notified and available to respond within this window, or they must implement retry mechanisms that regenerate the token, though the current action implementation does not inherently support token refresh during a pause.

Customization and Operational Workflow

The manual-approval action offers several customization options to fit diverse operational workflows. Users can specify the target repository for the approval issue. If target-repository or target-repository-owner is not provided or is empty, the issue is created in the repository where the workflow is running. This allows teams to centralize approval issues in a specific repository or keep them isolated within the service being deployed.

Custom keywords can be added to expand the set of approved and denied responses. This includes support for GitHub emojis, which can be stored in text form (e.g., :shipit:) or Unicode form (e.g., ✅). To ensure seamless configuration, it is recommended to compose custom words in a GitHub comment and copy them into the YAML configuration to avoid encoding issues.

Testing new versions of the action or custom forks requires specific steps. Developers can build and push images to a container registry, such as ghcr.io, and modify the action.yaml file to point to the new image. Testing involves running a workflow against a development branch that references the modified action. The release process involves creating a release branch, updating the action configuration, merging the changes, and pushing new tags.

Conclusion

The ability to introduce human oversight into automated CI/CD pipelines is a critical component of modern software engineering. GitHub’s native workflow_dispatch event provides a robust mechanism for initiating workflows on demand, offering flexibility through input parameters and branch selection without cluttering commit history. For scenarios requiring mandatory human approval within an automated flow, third-party actions like manual-approval fill a significant gap left by the licensing restrictions of GitHub Environments.

While these tools enhance operational safety, they introduce complexity regarding runner compatibility, security permissions, and timeout constraints. The reliance on Linux runners for approval actions, the 60-minute token expiration for team-based approvals, and the 35-day workflow limit require careful architectural consideration. By understanding these technical details, engineering teams can implement manual triggers and approval gates that are both secure and reliable, ensuring that automation aids rather than compromises the integrity of the deployment process.

Sources

  1. GitHub Actions manual triggers with workflow_dispatch
  2. Manual Workflow Approval
  3. GitHub Actions manual triggers with workflow_dispatch

Related Posts