Jobs in GitLab CI/CD can be set to run “manually” using the following option: when: manual, which looks something like this in the Pipeline view:

… requiring interaction from a user to proceed.

Note that all of the examples below are tested in the context of a Merge Request using the only: merge_requests setting on all the jobs.

Pipeline Status

The status of the pipeline itself while waiting for manual intervention depends on a few things.

allow_failure Is it the last stage? Pipeline status when waiting
false - Blocked
true (default) YES Success
true NO Blocked

Here’s what Blocked looks like:

Here’s what Success looks like:

Impact on Merge Request

When a pipeline is Blocked, the associated Merge Request cannot be merged at all.

Note that the “Merge When Pipeline Succeeds” button shows when then pipeline is in Running state. There’s also a “Merge Immediately” option at this point.

And only in the Success state is the simple “Merge” button shown.

Requiring Pipeline Success

So what happens if we turn on the “Pipelines must succeed” option?

In this case, the “Merge When Pipeline Succeeds” button is shown, but with a key difference - there is no option to “Merge Immediately”.

Even if the manual job is in the last stage, the Merge Request cannot be merged until it passes.

Note that if you click the “Merge When Pipeline Succeeds” button, it still won’t merge until the manual job completes successfully.

Merging manually

The “Pipelines must succeed” button doesn’t seem to prevent merging and pushing manually at the command line.

±[master]>> git fetch
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (4/4), done.
   242f1c0..a2cb417  2-do-thing-2 -> origin/2-do-thing-2

±[master]>> git merge origin/2-do-thing-2
Updating 242f1c0..a2cb417
 .gitlab-ci.yml | 6 ------      | 2 +-
 2 files changed, 1 insertion(+), 7 deletions(-)

±[A1][master]>> git push
Total 0 (delta 0), reused 0 (delta 0)
   242f1c0..a2cb417  master -> master

However, if I protect the master branch so that nobody can push to it:


±[A1][master]>> git push
Total 0 (delta 0), reused 0 (delta 0)
remote: GitLab: You are not allowed to push code to protected branches on this project.
 ! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to ''

Skipping CI entirely

There is still a way that developers can bypass the pipeline! That’s by passing in the [skip ci] commit message (or, presumably, by using the ci.skip option from the Git command line).

The only ways to prevent CI skipping are to disable it at the instance level and use a push rule.

The final setup

We had a customer ask about setting up a CI pipeline that would not run on every commit, but had to be run on the last commit in the Merge Request before the MR could be merged. Here’s the example solution.

  - test-light
  - test-heavy

    - sleep 1
  stage: test-light
    - merge_requests

    - sleep 1
  stage: test-heavy
  when: manual
  allow_failure: false
    - merge_requests

Note that the following settings must be in place for this to work as expected:

  • “Pipelines must succeed”
  • A Push Rule to prevent [skip ci] and [ci skip] (in any capitalization) in commit messages
  • A Gitaly setting to prevent the ci.skip option from being handled