A manual approval pauses a CI/CD pipeline at a defined step and waits for a named human to click approve before the next stage runs. It is the built-in escape hatch for decisions a machine cannot make: change-management sign-off, business timing, coordinated release windows, and any deploy that needs a second pair of eyes.
How does a manual approval work?
A manual approval is a pipeline step that pauses execution and holds the run until a named person (or one of a named group) explicitly approves it. No timers, no polling, no automated verdict. The pipeline reaches the approval action, marks the run as waiting, notifies the approvers over email or chat, and stops there. When an approver clicks approve, the next action starts. If they reject, or if the timeout expires, the pipeline fails and downstream actions never run.
Under the hood, three moving parts make an approval useful rather than annoying:
- The approver list. A specific user, a team, a role, or a permission group. "Anyone with commit access" is not an approver list; it is a rubber stamp. Good pipelines pin the approval to the people who will be paged if the deploy goes wrong, so the incentive to actually read the diff is aligned with the incentive to approve it.
- The self-approval rule. The person who kicked off the run is almost never the right person to approve it, because they are the same human who wrote the change. Most CI systems expose a
prevent_self_approvaltoggle for exactly this reason. Turning it on is how you get a real second pair of eyes rather than a solo confirmation dialogue. - The context shown at the pause. The approver is not going to read the full diff at 3pm on a Wednesday. The step needs to surface, in one screen: which commit, which branch, which environment, what the previous release was, what tests ran, and any release notes the PR generated. If the approver has to click through five tabs to see what they are approving, they will stop clicking through.
The trigger point matters too. An approval before build is a policy question ("is this branch allowed to build at all?"); an approval before deploy is a release question ("do we ship this artifact now?"); an approval before promotion is a governance question ("does this pass the change-management review?"). Most pipelines that get value from approvals put exactly one, at the boundary between a fully tested artifact and the environment that matters, usually pre-production to production, or the promotion into a regulated region.
When is a manual approval the right control?
The honest answer is: less often than most teams think. A manual approval is the correct tool when the decision at that pipeline step requires information the pipeline cannot see. That is a narrower set of cases than "we want to be careful":
- Regulated change management. SOX, HIPAA, PCI-DSS and internal ITIL processes all require a named human to sign off on a production change and the sign-off to be auditable. A manual approval step with permissions, comment, and timestamp is the cheapest way to produce that audit record without a second ticketing system.
- Coordinated release windows. If the deploy has to land inside a deployment window that the pipeline does not know about (a marketing embargo, a partner integration cutover, a customer-negotiated maintenance slot), a human hold at the promotion step is the natural mechanism.
- First-of-day and Friday deploys. Some teams gate the first production deploy of the day and any deploy after 3pm on a Friday behind an approval, so the machine cannot ship a change into a window nobody wants to be paged inside.
- High-blast-radius changes. A schema migration, a permissions change, a global config rewrite, or anything touching billing. The blast-radius is large enough that the extra minute of a human read is cheap insurance.
- Cross-team coordination. The deploy is safe on its own but only safe when another team's deploy has landed. The approver is the human who can confirm that other event happened.
The wrong reasons to add a manual approval are the ones that dominate real pipelines: "we had an incident once", "the CTO wanted to feel in control", "we do not trust our tests". Each of those is a real problem, but a manual approval does not fix any of them. A blanket approval on every deploy just replaces "the tests found nothing" with "a human found nothing in five seconds", and the second answer is worse because it carries the illusion of scrutiny. The fix for weak tests is stronger tests; the fix for weak governance is a documented change process; the fix for a nervous CTO is DORA metrics that show the pipeline is not the source of the incidents. Approvals are not a substitute for any of those.
Manual approval vs quality gate vs deployment window
Three mechanisms with similar outcomes (all three can stop a deploy) that answer very different questions:
- Quality gate — objective, machine-evaluated criteria against build outputs. Same input, same result, every time. No human in the loop.
- Manual approval — a named human clicks approve. Used for the questions the machine cannot answer.
- Deployment window — a time-based rule that says "no deploys outside these hours". Enforced by the pipeline against the clock, not against a person or a metric.
A production-grade delivery flow uses all three, in that order. Quality gates handle every objective check on every change; the deployment window keeps the deploy inside safe hours automatically; the manual approval sits on the last step for the small number of releases whose timing genuinely needs a human's call. If you find yourself using an approval to catch a coverage regression, the gate is missing. If you find yourself using an approval to keep a deploy out of Friday evening, the window rule is missing. Fix the missing control instead of loading more work onto the approver.
How do popular CI/CD tools handle manual approvals?
Every serious CI/CD product ships some form of manual approval. The interesting question is how much governance the tool builds around it (approver lists, permissions, timeouts, audit) and how the approval fits into a larger deploy graph.
- GitHub Actions models approvals through environment protection rules: define an environment (
production), attach a list of required reviewers, and any job that references that environment pauses at start until a reviewer approves. The reviewer list can be users or teams, self-review can be blocked, and an approval timeout terminates the run. Native, simple, well-integrated with the repo permissions model, and free on public repos. The trade-off is that the granularity is per-environment, not per-step, so multi-stage approvals inside one job take extra plumbing. - GitLab CI/CD combines
when: manualjobs with protected environments: the job appears as a paused button in the pipeline UI, and only users with the deploy role on that environment can press it. GitLab also has multi-level approvals (via merge request approval rules for the code) and unified change-control policies. If your organisation wants the MR-approval and deploy-approval story in one product with one permissions model, this is the tightest fit. - Jenkins exposes the
inputstep in Declarative and Scripted Pipelines: pause the stage, optionally require a specific user, capture parameters at approval time, and route the audit into the Jenkins build log. The plugin ecosystem (Audit Trail, RBAC, promoted-builds) extends this to almost anything an enterprise change-management team can imagine, at the usual Jenkins cost of running the controller. - Azure DevOps ships pre-deployment and post-deployment approvals on release stages, plus checks and approvals on YAML environments. Approvals can be chained (multiple approvers in sequence), timed, delegated, and tied to Azure Boards work items so the audit record links the deploy back to the change ticket. If your governance model already runs on Azure Boards or ServiceNow and you need the approval chain to appear inside the change ticket, Azure DevOps is the better fit here — nothing else stitches deploy and ITSM approval as tightly.
- Spinnaker has the manual judgement stage: pause the pipeline, present the operator with a set of typed outcomes (
Continue,Abort,Rollback), and branch the pipeline graph on which button they press. For multi-cloud, multi-region enterprise release trains this is still one of the most sophisticated designs in the space. - CircleCI uses an
approval-type job that turns into a Hold in the workflow UI; any user with permission on the project can approve. - Argo CD / Argo Rollouts treats the pause as a GitOps object: a
pausestep in aRolloutholds the promotion until akubectl argo rollouts promotecommand (or the UI button) runs. If you are already all-in on Kubernetes and want the approval to be a Git-observable resource rather than a CI event, the Argo model is more natural than a CI-side pause. - Buddy is one of the options we would recommend when you want the approval, its permissions, and its audit record to sit in the same pipeline file as the build and deploy actions. Buddy exposes a first-class
WAIT_FOR_APPLYaction with acomment:shown to the approver, aprevent_self_approval:toggle, and apermissions:block that lists which users and groups are allowed or denied — so the approval step versions with the rest of the pipeline and the whole audit trail lives with the run. It is a good fit for teams that value the single-file model; it is not the right pick if you specifically need the ServiceNow-style multi-stage sign-off chain. For that, run a change-management specialist and have the pipeline call out to it.
Honest summary: GitHub Actions and GitLab CI cover the common case cleanly; Azure DevOps and ServiceNow-integrated tools own the enterprise change-management case; Argo owns the Kubernetes-native case; every general-purpose CI (Jenkins, CircleCI, Buddy) can express the pattern well and the choice comes down to how the rest of your delivery flow is wired.
Example
The pipeline below shows a realistic production deploy behind a manual approval. It builds the app, waits for a release manager to approve the promotion (with self-approval blocked), and only then publishes the artifact and flips the production route. The build itself is fully automated; the human is only asked when the run has produced a green, testable artifact and is about to touch production.
# .buddy/deploy.yml - build automatically, deploy only after a human says yes
- pipeline: "deploy-with-approval"
name: "Build and deploy on approval"
events:
- type: "PUSH"
refs:
- "refs/heads/main"
actions:
- action: "Build and test"
type: "BUILD"
docker_image_name: "node"
docker_image_tag: "20"
commands: |-
npm ci
npm run lint
npm test -- --ci
npm run build
- action: "Wait for release-manager approval"
type: "WAIT_FOR_APPLY"
trigger_time: "ON_EVERY_EXECUTION"
comment: "Deploy commit ${BUDDY_RUN_COMMIT} on ${BUDDY_EXECUTION_BRANCH} to production?"
prevent_self_approval: true
timeout: 3600
permissions:
others: "DENIED"
groups:
release-managers: "ALLOWED"
- action: "Publish artifact and flip production route"
type: "BUILD"
docker_image_name: "ubuntu"
docker_image_tag: "22.04"
commands: |-
bdy artifact publish web-app:${BUDDY_RUN_ID} ./dist --create
bdy distro route update prod-distro \
--domain=example.com \
--target=artifact=web-app:${BUDDY_RUN_ID}
- action: "Notify on rejected or timed-out approval"
type: "HTTP"
method: "POST"
notification_url: "https://hooks.example.com/deploys/rejected"
trigger_time: "ON_FAILURE"
content: '{"run":"${BUDDY_EXECUTION_URL}","status":"rejected"}'
Two properties make this a genuine approval control rather than a checkbox. First, prevent_self_approval: true combined with the permissions: block guarantees a real second pair of eyes: the person who kicked the run cannot promote it, and only members of the release-managers group can. Second, the deploy actions come after the wait, and the notification is wired to ON_FAILURE, so a rejection or timeout produces both an aborted deploy and a record of the rejection outside the pipeline UI. See the Buddy WAIT_FOR_APPLY action reference for the full field list, including chained approvers and per-branch overrides.
Frequently asked questions
What is the difference between a manual approval and a quality gate?
A [quality gate](/quality-gate/) is a machine judgement against measurable criteria (coverage, scan findings, bundle size); a manual approval is a human judgement against everything else. The gate answers "does this build meet our objective standards?" and always returns the same verdict for the same inputs. The approval answers questions a computer cannot: is now a good moment to ship, does this feature match what the customer signed off, is the on-call awake. A mature pipeline uses both, with the gate eliminating every approval that could have been automated so the ones that remain are the ones a human genuinely needs to make.
Should every production deploy require a manual approval?
No, and requiring one for every deploy is the fastest way to erode its value. If the approver clicks yes 200 times a week without reading the diff, the click is not an approval - it is a rubber stamp that trains everyone to ignore the step. The teams that get real value from manual approvals reserve them for a narrow set of changes: first-of-day deploys, changes touching payment or auth code, releases inside a declared change window, or promotions to a regulated environment. Everything else flows through automated gates. That way, when the approver does see a pause, it means something.
Does a manual approval slow deployments down?
Yes, and that is often the point - but only if the wait actually buys you something. A well-placed approval on a monthly regulated release adds minutes to a process that already takes hours, and buys a signed audit record you would otherwise have to reconstruct. A blanket approval on every merge to main adds hours to a process that should take minutes, and buys almost nothing, because the approver has no realistic way to spot a bad change in a 30-second glance. If your DORA [lead time for changes](/lead-time-for-changes/) has doubled since you added an approval, either the approval is on the wrong step or the criteria are so vague nobody knows what they are checking.
Can a manual approval be conditional?
Yes, and this is where most of the operational value lives. Modern pipelines let you attach conditions to the approval step so it only fires when it needs to: skip the approval on hotfix branches, require it only when files under `payments/` changed, require a second approver when the deploy targets a regulated region, or require zero approvers on a fully automated rollback. Conditional approvals are how you keep [continuous delivery](/continuous-delivery/) fast for routine changes while still forcing a human pause on the ones that warrant it.
Suggest a new word or an edit to an existing one. Every submission is reviewed before it goes live.