DEV Community

Wen Ping
Wen Ping

Posted on Edited on Originally published at anguskit.com

The pipeline stops at an approval: who can let it continue

This article explains how to make a pipeline stop and wait for a person before a production deploy continues. After reading it, you can add approval on a single job in YAML, or set an approval policy for the whole pipeline in the console, and you will know that the approver clicks Approve under CI/CD → Approvals. The person who triggered this run cannot approve their own run.

This approval decides whether this run may continue. It is separate from code review on a pull request. Pipelines are available on Team and Enterprise. Community has no CI/CD menu, and the steps in this article do not apply there.

The example is .anguscicd/05-deploy-approval.yaml in xlliu/test-cicd-example. In the console the pipeline name is test-cicd-example · 05 Deploy Approval. The name inside the file is deploy-with-approval. As the YAML is written, only the last job waits for a person. This pipeline currently also has an approval policy enabled, so a new run stops for approval before every job. Section 3 covers that.

As the YAML is written, build and deploy-staging finish, then deploy-production stops and waits for approval

1. Where the approval stops

Approval is attached to a job, and it takes effect before the job starts. When that job’s turn comes, the platform checks whether it needs approval. If it does, the platform creates an approval request and the job waits. Until the approval ends, this job is not assigned a runner (the program on a build machine that runs the commands) and it runs no steps.

An approval ends in one of three ways:

  • Approve: the job continues. A runner is assigned and the steps run.
  • Reject: the job fails, and later jobs that depend on it do not run.
  • Timeout: the pass condition was not met within the wait. The job fails. Clicking approve after the timeout does not change that.

Approval usually sits on a deploy job that depends on the build and test jobs ahead of it. Approvers are asked only after those earlier jobs have succeeded.

2. Add approval on one job in YAML

.anguscicd/05-deploy-approval.yaml has three jobs. build runs the sample program. deploy-staging prints one deploy line after build succeeds. deploy-production waits for a person after deploy-staging, and only after approval does it run its two echo steps. A push to main or a tag starts this pipeline. deploy-production also has if: ${{ branch == 'main' }}, so a run started by a tag does not enter this step.

version: "1"
name: deploy-with-approval

triggers:
  - on: push
    branches:
      - main
  - on: tag

env:
  APP_NAME: hello-node

pipeline:
  build:
    runner:
      mode: DOCKER
      image: node:20-alpine
    steps:
      - name: Package
        run: |
          node apps/hello-node/src/index.js
          echo "artifact=dist/${APP_NAME}.tgz"
        timeout: 180

  deploy-staging:
    needs: ["build"]
    environment: staging
    runner:
      mode: DOCKER
      image: node:20-alpine
    steps:
      - name: Deploy to staging
        run: echo "deploy staging ${APP_NAME} sha=${GIT_COMMIT_SHA:-local}"
        timeout: 120

  deploy-production:
    needs: ["deploy-staging"]
    environment: production
    if: ${{ branch == 'main' }}
    canaryWeight: 20
    approval:
      required: true
      approvers: [376747068704423936, 1]
      policy: any
      timeout: 7200
    runner:
      mode: DOCKER
      image: node:20-alpine
    steps:
      - name: Canary rollout 20%
        run: echo "canary 20% for ${APP_NAME}"
        timeout: 120
      - name: Promote to 100%
        run: echo "promote 100% for ${APP_NAME}"
        timeout: 120
Enter fullscreen mode Exit fullscreen mode

The file comments name the approvers as 柳小龙 and admin. In the approval record, the first is shown as Angus, user id 376747068704423936. admin’s user id is 1. policy: any means either person is enough. timeout: 7200 means wait at most 7200 seconds, which is 2 hours.

Two other fields are easy to misread. environment: production only records the environment name for this deploy. Writing production does not start an approval by itself. canaryWeight: 20 is only checked to be between 0 and 100. The platform does not split traffic by that ratio. The builder hint is “Weight (0-100; script convention, not applied by the platform)”. The two steps in the sample are only echo. They do not publish anything.

The fields inside approval:

Field Type and default What it does
required Boolean Whether a person must approve. true is what makes the job wait
approvers Array of numbers The approver list. Use numeric user ids, not login names. An empty list does not wait
policy Default any any means any one person is enough. all means everyone on the list must approve. quorum means the number of approvals reaches requiredApprovals
requiredApprovals Positive integer How many people must approve. Used only with policy: quorum
timeout Default 3600 (seconds) How many seconds to wait. If it is still not approved, the job fails

If you do not know the user id, set it in the pipeline editor. Select deploy-production, open Approval, turn on Require approval, and click Pick approver. Search by name or username and select the person. The timeout field is labeled Timeout (hours), and the hint beside it is “Saved as {seconds} seconds”. If no approver is selected, the editor says “With no approvers selected, the approval gate is skipped at runtime. Pick approvers to keep the gate.” The gate here is the point in the pipeline that must be satisfied before the job is released. In this article, that gate is the human approval.

A user id is a long number. The console stores it as written and does not treat it as an ordinary number. Treated as an ordinary number, the last digits can change. Section 3 has an example. The 376747068704423936 in the YAML above stays in the file as written.

To name approvers by team, or to leave the YAML unchanged, use an approval policy in the next section.

3. Leave the YAML alone: set an approval policy for the whole pipeline

An approval policy is configured in the console. The repository YAML stays as it is. Two facts matter before you use one. An enabled policy applies to every job on this pipeline. It overrides the approval block in the YAML.

  1. Open approval policies Open CI/CD → Governance, switch to the Approval Policies tab, and click New Policy.
  2. Pick the pipeline and name the policy Pipeline is the pipeline you want to control. Name is a name you can recognize later, for example “Production deploy gate”. The placeholder is “e.g. Production deploy gate”.
  3. Pick approvers and the pass rule Turn on Require manual approval. Then search and select users under Approvers, or select a team under Approver teams (every member of the team becomes an approver). Together they need at least one person or one team. Policy has four options: Any one approves, All must approve, Quorum reached (the number of approvals reaches the value you set), and Per-group quorum, which YAML does not have. The group hint is “Each group must meet its required count (AND across groups).” That fits a rule such as one person from development and one person from operations. Then fill in Timeout (seconds).
  4. Enable it Turn on Enabled and save. The policy takes effect immediately. On every later run of this pipeline, every job waits for approval first.

On the pipeline detail Approvals tab, you can see which config is in force. The page shows one of these two sentences: “This pipeline is governed by approval policy “{name}” (enabled); it overrides the YAML approval config at runtime.” Or: “No pipeline-level approval policy configured; the YAML job-level approval config applies at runtime.”

Two more points when you use a policy:

  • The scope is every job. If only the deploy job should wait, and build and test should run on their own, the YAML approval is the better fit.
  • Disabling or deleting the policy cancels approvals that are still waiting. Those jobs then follow the YAML. If the YAML also requires approval, a new approval request is created. If the YAML does not require it, the job runs. Also, if the policy stays enabled and Require manual approval is turned off, every job on this pipeline stops waiting, and the YAML approval no longer applies. The hint is: “On: every job on this pipeline waits for approval. Off, while the policy stays enabled: those jobs run through, and YAML approval no longer applies.”

Back to the example. test-cicd-example · 05 Deploy Approval already has an enabled policy named E2E Deploy Approval. It has Require manual approval on, the policy is Any one approves, the timeout is 3600 seconds (the hint under the field is = 1h), and the approver chips (the small tags for selected users) are Angus and admin. On later runs of this pipeline, build, deploy-staging, and deploy-production all wait for approval first. The YAML that blocked only the last job is no longer what runs.

The approvers saved on this policy now are admin (1) and Angus (376747068704423936), the same two ids as in the YAML. An older save stored Angus’s id as 376747068704423940. The chip on the old page still showed Angus. Opened with the current frontend, it shows #376747068704423940. When that happens, remove the chip, select Angus by name again, and save, so the stored id matches the YAML. Clicking save alone writes the wrong number back as it is.

Editing the enabled policy E2E Deploy Approval, timeout 3600 seconds, approvers Angus and admin

The picture is the saved Edit Approval Policy form: the approvers are admin and Angus, Enabled and Require manual approval are both on, and the name placeholder is still “e.g. Production deploy gate”. Viewing the policy does not require another click on Save.

4. Where an approver acts

After an approval request is created, the system does not push a notification. The approver looks in these places:

  • CI/CD → Approvals: the page title is Approvals, and the description is “Handle pipeline jobs that are waiting for a person to approve them.” On the Pending tab, Assigned to me shows only requests that need you, and All pending shows everyone’s requests.
  • The Needs attention area on CI/CD → Overview, and the Approvals tab on the pipeline detail page. Both can approve or reject the same requests.
  • The home to-do list. Items of the kind CI approval appear there too.

The steps:

  1. Open the detail In the Pending list, click Details on a request. The detail page has Run context (which pipeline, which job, the branch, the commit, how it was triggered, and who triggered it) and Approval progress. While the request is still waiting, the progress reads like “1 vote(s) left · 0/1”, with a policy line such as “Any one approver is enough”. To look at the change itself, click Open run.
  2. Click Approve or Reject, then add a comment While the request is still waiting, the detail page has Approve and Reject. Either one opens a confirmation. Comment inside it can be left empty. The placeholder is “Add an approval note (optional)”. The comment is entered only in that confirmation. The detail page body has no input.
  3. Confirm the result After you submit, a short message says “Approved” or “Rejected”. The result sentence on the detail page is a different line. On approve it is “Approved. The pipeline continued.” On reject it is “The approval was rejected and the job failed.” On timeout it is “The approval timed out and the job failed.” Handled requests move to History. For a request that has already ended, the detail page no longer shows Approve and Reject. It keeps Open run and Re-run.

If the list has no pending request right now, open a record that already passed and compare. This record from 2026-08-24 is one: approval detail for deploy-production. It was triggered by a push, the branch is main, the commit is 98d2f2a, and the person who triggered it is Angus. The wait window is 17:50 to 19:50, exactly 7200 seconds, matching timeout: 7200 in the YAML. The policy enabled later is 3600 seconds, so this run followed the YAML approval. admin clicked approve at 17:52. Angus is shown as Not required. The progress reads “Met 1/1 · roster of 2”.

Passed deploy-production: Angus is Not required, admin is Approved

The picture has no Approve and no Reject, because this approval has ended. To run this job again, use Re-run on the run detail. Those two buttons do not come back on an approval that has ended.

5. Who can click Approve

A person who clicks Approve has to meet all three of these:

  1. They are on the approver list. Someone outside the list who clicks approve sees “You are not an approver for this request.” If approvers were chosen by team, the list is fixed from the team members at the moment the request was created.
  2. They are not the person who triggered this run. For a run started by a push, the person who pushed is the trigger. For a manual run, the person who clicked Trigger is the trigger. If that person clicks approve, the message is “You cannot approve a run you triggered. Wait for another approver.” They can click Reject, which stops a deploy they started.
  3. They have permission to handle approvals, and they can open this repository. The role has to include Process Approval under CI/CD → Approvals. A system administrator assigns that permission on the role.

Also, if any approver clicks Reject, the request ends, the job fails, and the others do not need to act.

Item 2 is the one that most often leaves an approval stuck. If the list contains only you, a run you started by pushing has nobody who can approve it. It waits until it fails on timeout. policy: all has the same problem: if the person who triggered the run is on the list, “everyone must approve” can never be reached. Put at least one person on the list who will not trigger this pipeline, or change the policy to any or quorum. The record above is that case: Angus pushed main and could not approve their own run. The job continued after admin approved.

6. The approval stays stuck, or it never appears

When approval does not show up as you expected, or the result never arrives, use this table:

What you see Why What to do
The job has required: true and runs straight through approvers is empty, or an approval policy is enabled with Require manual approval turned off Put user ids in approvers. On the pipeline detail Approvals tab, see which config is in force
Build and test jobs are also waiting for approval The pipeline has an approval policy enabled, and the policy applies to every job (05 Deploy Approval is in that state now) To approve only deploy-production, disable E2E Deploy Approval first and use the YAML approval
Approve says “You cannot approve a run you triggered. Wait for another approver.” You triggered this run Ask someone else on the list. Later, put one more person on the list
Approve says “You are not an approver for this request.” You are not on the list, or you joined the team after the request was created Ask someone on the list, or change the config and run again
An approver says they never saw the request No notification is sent today Ask them to open CI/CD → Approvals and look at Assigned to me
“All must approve” is never reached, and it times out The person who triggered the run is also on the list, and they cannot approve their own run Change policy to any or quorum, or take that person off the list
The job fails, and History shows a timeout The pass condition was not met within timeout Raise timeout, or have an approver act in time and run again

Common questions

How is this different from code review on a pull request?

Code review decides whether this change can be merged. It looks at the change itself. Pipeline approval decides whether this run can continue. It usually happens after the code is merged and before deploy. The two can be used together: merge after review, let the build from that merge finish, then have the owner approve the deploy.

After a reject or a timeout, how do I start again?

The approval result belongs to this run. A failed job cannot be changed into a pass. To start again, open the request from History, click Details, and click Re-run. That starts a new run, and a new approval request is created when it reaches the approval job. You can also push the code again, or click Trigger on the pipeline page and run it once by hand.

How long are approval records kept, and can I see who approved?

Passed, rejected, and timed-out requests stay in History. Approval progress on the detail page lists each approver’s status (Approved, Rejected, or Not required), plus the time and the comment.

Summary

Two setups make a pipeline stop and wait for a person. To block one job, add approval in the YAML, put the full user ids in approvers, and set policy to any, all, or quorum. To leave the YAML alone and pick people by name or by team, configure CI/CD → Governance → Approval Policies. An enabled policy applies to every job on that pipeline and overrides the YAML. The sample pipeline 05 Deploy Approval is currently overridden by E2E Deploy Approval, and the timeout became 3600 seconds. Approvers act under CI/CD → Approvals. They have to be on the list, and they cannot approve a run they triggered. If anyone rejects, or the wait times out, the job fails. The usual way an approval gets stuck is a list that contains only the person who triggered the run.

Conclusion: To block only the deploy job, write approval on that job in the YAML and fill in the full user id, with no truncated digits, and confirm this pipeline has no approval policy enabled (otherwise a new run follows the policy). The job continues after an approver clicks Approve under CI/CD → Approvals. The person who triggered the run cannot approve that run.

Further reading

Top comments (0)