Skip job in a pipeline based on exit code

Everyone can contribute. Help move this issue forward while earning points, leveling up and collecting rewards.

This is a proposal based on the discussion in #16733 (comment 900108650) which inspired by the proposal in #273157 (closed)

Problem

Users want to skip a specific job based on the job exit code

Proposal

test_job:
  script:
    - execute_script 
# if the script exit code is 137 or 255 this specific job will be skiped and the pipeline will continue to run
  allow_skip: # Or Skip or skip_job
    exit_codes: # User defined exit code
      - 137
      - 255

in case we have and test job had skipped

build job:
  needs: test_job

build job will also get skipped

History, Explanation, and Proposal

Today, we have the allow_failure:exit_codes keyword, which you dynamically mark your job allow_failure: true depending on the job exit code.

test_job_1:
  script:
    - echo "Run a script that results in exit code 1. This job fails."
    - exit 1
  allow_failure:
    exit_codes: 137

test_job_2:
  script:
    - echo "Run a script that results in exit code 137. This job is allowed to fail."
    - exit 137
  allow_failure:
    exit_codes:
      - 137
      - 255

Now, in this issue, the ask is something like this;

test_job_1:
  script:
    - echo "Run a script that results in exit code 1. This job fails."
    - exit 1
  skip:
    exit_codes: 137

test_job_2:
  script:
    - echo "Run a script that results in exit code 137. This job is skipped."
    - exit 137
  skip:
    exit_codes:
      - 137
      - 255

Of course, I'd prefer to combine them;

test:
  script:
    - ...
  exit_codes: # or `statuses` as in Marcel's suggestion: https://gitlab.com/gitlab-org/gitlab/-/issues/357819#note_902816616
    skip:
      - 1
      - 2
    allow_failure:
      - 3
      - 4

Now, let's see if this is technically possible...

When a job finishes, the Runner sends an update to GitLab about it with some parameters including state, exit_code, etc. The API endpoint calls Ci::UpdateBuildStateService and runs the code that updates the job status.

The decision of whether a job is completed success or failure is on the Runner. When a job finishes other than the exit code 0, it's a failed status. Let's say we want this;

test:
  script: exit 123
  exit_codes:
    skip: [123]
  • This job will fail because it completes its exits with a non-zero status.
  • The Runner will send state: failed, exit_code: 123.
  • The GitLab runs Ci::UpdateBuildStateService for the build.
  • Since it's a failed status, we'll run build.drop_with_exit_code!(params[:failure_reason], params[:exit_code]).

Let's hack this for now;

    def drop_with_exit_code!(failure_reason, exit_code)
      skip! if exit_code == 123 # <<-- HACK

      failure_reason ||= :unknown_failure
      result = drop!(::Gitlab::Ci::Build::Status::Reason.new(self, failure_reason, exit_code))
      ::Ci::TrackFailedBuildWorker.perform_async(id, exit_code, failure_reason)
      result
    end
Click to expand
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -1115,6 +1115,8 @@ def debug_mode?
     end

     def drop_with_exit_code!(failure_reason, exit_code)
+      skip! if exit_code == 123
+
       failure_reason ||= :unknown_failure
       result = drop!(::Gitlab::Ci::Build::Status::Reason.new(self, failure_reason, exit_code))
       ::Ci::TrackFailedBuildWorker.perform_async(id, exit_code, failure_reason)

--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -147,7 +147,7 @@ class CommitStatus < Ci::ApplicationRecord
     end

     event :skip do
-      transition [:created, :waiting_for_resource, :preparing, :pending] => :skipped
+      transition [:created, :waiting_for_resource, :preparing, :pending, :running] => :skipped
     end

     event :drop do

Result;

Screenshot_2025-02-06_at_13.55.45

It "works" ⁉️

Edited by 🤖 GitLab Bot 🤖