Edit

Share via


Pipeline conditions

Azure DevOps Services | Azure DevOps Server 2022 - Azure DevOps Server 2019

This article describes the different conditions that allow an Azure Pipelines stage, job, or step to run, and how to set those conditions in a YAML pipeline definition.

Note

This article discusses YAML pipeline capabilities. For Classic pipelines, you can specify some conditions under which tasks or jobs run in the Control Options of each task, and in the Additional options for a job in a release pipeline.

Conditions a stage, job, or step runs under

By default, a pipeline job or stage runs if it doesn't depend on any other job or stage, or if all its dependencies completed and succeeded. The dependency requirement applies to direct dependencies and to their indirect dependencies, computed recursively.

By default, a step runs if nothing in its job failed yet and the step immediately preceding it completed. For more context on stages, jobs, and steps, see Key concepts for Azure Pipelines.

You can override or customize these default behaviors by setting a stage, job, or step to run even if or only if a previous dependency fails or has another outcome. You can also define custom conditions. In a YAML pipeline definition, you use the condition property to specify conditions under which a stage, job, or step can run.

Note

Conditions apply to all previous direct and indirect dependencies with the same agent pool. Stages or jobs in different agent pools run concurrently.

Conditions based on previous dependency status include:

  • Succeeded: Run only if all previous dependencies succeed. This behavior is the default if no condition is set in the YAML. To apply this condition, specify condition: succeeded().
  • Succeeded or failed: Run even if a previous dependency fails, unless the run is canceled. To apply this condition, specify condition: succeededOrFailed().
  • Always: Run even if a previous dependency fails, even if the run is canceled. To apply this condition, specify condition: always().
  • Failed: Run only when a previous dependency fails. To apply this condition, specify condition: failed().

Important

When you specify a condition property for a stage, job, or step, you overwrite the default condition. Your stage, job, or step might run even if the build is canceled. Make sure your conditions take the state of the parent stage or job into account.

Condition example

The following YAML example demonstrates the always() and failed() conditions. The first script task in job 1 has an always condition, so it runs even if dependencies fail or the build is canceled. In the second script task, exit job1 forces the job1 job to fail.

Pipeline stages run sequentially by default, but jobs can run in parallel. You can use the dependsOn property to explicitly define dependencies between stages or jobs.

To set the conditions for a job that depends on the outcome of another job, use dependsOn to define the dependency. In the following example, job2 depends on job1 and runs because job1 fails.

jobs:
- job: job1
  steps:
  - script: echo Hello!
    condition: always() # this step runs even if the build is canceled
  - script: |
      echo "This task will fail."
      exit job1 
- job: job2
  dependsOn: job1
  condition: failed() # this job runs only if job1 fails

Note

You can also use the Azure Pipelines UI to manually run dependent stages when the parent stage fails. For more information, see Run children stages when parent stage fails.

Custom conditions

If the built-in conditions don't meet your needs, you can specify custom conditions as expressions in YAML pipeline definitions.

The agent evaluates the expression beginning with the innermost function and proceeding outward. The final result is a boolean value that determines whether or not to run the stage, job, or step. For a full guide to the syntax, see Expressions.

Important

Conditions are evaluated to determine whether to start a stage, job, or step. Therefore, nothing computed during the runtime of a stage, job, or step is available to use within that same stage, job, or step. For example, if you set a variable in a job using a runtime expression with $[ ] syntax, you can't use that variable in conditions within that job.

Variables in conditions

You can set pipeline variables and use them in conditions. The following pipeline sets an isMain variable and uses it in a condition that runs Stage B only when the build source branch is main.

variables:
  isMain: $[eq(variables['Build.SourceBranch'], 'refs/heads/main')]

stages:
- stage: A
  jobs:
  - job: A1
    steps:
      - script: echo Hello Stage A!
- stage: B
  condition: and(succeeded(), eq(variables.isMain, true))
  jobs:
  - job: B1
    steps:
      - script: echo Hello Stage B!
      - script: echo $(isMain)

You can set a condition to run if a variable is null or an empty string. All variables are treated as strings in Azure Pipelines, so an empty string is equivalent to null in the following pipeline:

variables:
- name: testEmpty
  value: ''

jobs:
  - job: A
    steps:
    - script: echo testEmpty is blank
    condition: eq(variables.testEmpty, '')

Job output variables used in other job conditions

You can create a variable in a job that other jobs in the same stage can specify in conditions. Variables available to dependent jobs must be marked as multi-job output variables by using isOutput=true, as in the following code:

jobs:
- job: A
  steps:
  - bash: |
      echo "This is job A."
      echo "##vso[task.setvariable variable=doThing;isOutput=true]Yes" #set variable doThing to Yes
    name: DetermineResult
- job: B
  dependsOn: A
  condition: eq(dependencies.A.outputs['DetermineResult.doThing'], 'Yes') #map doThing and check the value
  steps:
  - script: echo "Job A ran and doThing is Yes."

Step variables used in subsequent step conditions

You can create a variable in a step that future steps in the same job can specify in conditions. Variables created from steps are available to future steps in the job by default and don't need to be marked as multi-job output variables.

Variables created in a step in a job have the following limitations:

  • Are scoped to the steps in the same job.
  • Are available in subsequent steps only as environment variables.
  • Can't be used in the same step that defines them.

The following example creates a pipeline variable in a step and uses the variable in a later step's script condition.

steps:

# This step creates a new pipeline variable: doThing. This variable is available to subsequent steps.
- bash: |
    echo "##vso[task.setvariable variable=doThing]Yes"
  displayName: Step 1

# This step uses doThing in its condition
- script: |
    # Access the variable from Step 1 as an environment variable.
    echo "Value of doThing (as DOTHING env var): $DOTHING."
  displayName: Step 2
  condition: and(succeeded(), eq(variables['doThing'], 'Yes')) # or and(succeeded(), eq(variables.doThing, 'Yes'))

Condition settings for various outcomes

The following table shows condition settings to produce various desired outcomes.

Desired outcome Example condition setting
Run if the source branch is main, even if the parent or preceding stage, job, or step failed or was canceled. eq(variables['Build.SourceBranch'], 'refs/heads/main')
Run if the source branch is main and the parent or preceding stage, job, or step succeeded. and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
Run if the source branch isn't main, and the parent or preceding stage, job, or step succeeded. and(succeeded(), ne(variables['Build.SourceBranch'], 'refs/heads/main'))
Run for user branches if the parent or preceding stage, job, or step succeeded. and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/heads/users/'))
Run for continuous integration (CI) builds, if the parent or preceding stage, job, or step succeeded. and(succeeded(), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI'))
Run if a pull request triggered the build and the parent or preceding stage, job, or step failed. and(failed(), eq(variables['Build.Reason'], 'PullRequest'))
Run for a scheduled build, even if the parent or preceding stage, job, or step failed or was canceled. eq(variables['Build.Reason'], 'Schedule')
Run if the System.debug variable is set to true, even if the parent or preceding stage, job, or step failed or was canceled. eq(variables['System.debug'], true)

Note

Release.Artifacts.{artifact-alias}.SourceBranch is equivalent to Build.SourceBranch.

Condition outcomes when a build is canceled

Canceling a build doesn't mean that all its stages, jobs, and steps stop running. Which jobs, stages, or steps stop running depend on the conditions you specified, and at what point of the pipeline's execution you canceled the build. If a stage, job, or step's parent is skipped, the stage, job, or step doesn't run, regardless of its conditions.

A stage, job, or step runs whenever its conditions evaluate to true. If a condition doesn't account for the state of the task's parent, the task might run even if its parent is canceled. To control whether jobs, stages, or steps run when a build is canceled, include a job status check function in your conditions.

If you cancel a build while it's in the queue stage but not yet running, the entire run is canceled, including all other stages.

Note

If any of your conditions make it possible for tasks to run even after the build is canceled, specify a value for cancel timeout that provides enough time for the tasks to complete after the run is canceled.

Example stage condition outcomes

The following examples show the outcomes of various conditions set on stages when the build is canceled.

Stage example 1

In the following pipeline, by default stage2 would depend on stage1 completing successfully. However, stage2 has a condition set to run whenever the source branch is main, regardless of stage1 status.

If you queue a build on the main branch and cancel it while stage1 is running, stage2 still runs, because eq(variables['Build.SourceBranch'], 'refs/heads/main') evaluates to true.

stages:
- stage: stage1
  jobs:
  - job: A
    steps:
      - script: echo 1; sleep 30
- stage: stage2
  condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
  jobs:
  - job: B
    steps:
      - script: echo 2

Stage example 2

In the following pipeline, by default stage2 depends on stage1 completing successfully. Job B in stage2 has a condition set to run whenever the source branch is main.

If you queue a build on the main branch and cancel it while stage1 is running, stage2 and its jobs don't run at all, even though the stage contains a job whose condition evaluates to true.

stages:
- stage: stage1
  jobs:
  - job: A
    steps:
      - script: echo 1; sleep 30
- stage: stage2
  jobs:
  - job: B
    condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
    steps:
      - script: echo 2

Stage example 3

In the following pipeline, by default stage2 depends on stage1 completing successfully. The step inside job B within stage2 has a condition set to run whenever the source branch is main.

If you queue a build on the main branch and cancel it while stage1 is running, stage2 and job B don't run at all, even though job B contains a step whose condition evaluates to true. Stage2 is skipped entirely because stage1 was canceled.

stages:
- stage: stage1
  jobs:
  - job: A
    steps:
      - script: echo 1; sleep 30
- stage: stage2
  jobs:
  - job: B
    steps:
      - script: echo 2
        condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')

Example job condition outcomes

The following examples show the outcomes of various conditions set on jobs when the build is canceled.

Job example 1

In the following YAML pipeline, job B running depends on job A running. Job B also has a condition set to run whenever the source branch is main.

If you queue a build on the main branch and cancel it while job A is running, job B still runs, because condition: eq(variables['Build.SourceBranch'], 'refs/heads/main') evaluates to true.

jobs:
- job: A
  steps:
  - script: sleep 30
- job: B
  dependsOn: A 
  condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')
  steps:
    - script: echo step 2.1

If you want job B to run only when job A succeeds and the build source is main, you must set the condition to and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')).

Job example 2

In the following YAML pipeline, job B depends on job A succeeding. Job B has a condition set to run whenever job A succeeds and the build source branch is main.

If you queue a build on the main branch and cancel it while job A is running, job B doesn't run, even though it has one condition that evaluates to true. The condition on job B evaluates to false because job A didn't succeed. Therefore, job B and its steps are skipped.

jobs:
- job: A
  steps:
  - script: sleep 30
- job: B
  dependsOn: A 
  steps:
    - script: echo step 2.1
  condition: and(eq(variables['Build.SourceBranch'], 'refs/heads/main'), succeeded())

Example step condition outcome

You can also set conditions on steps. In the following pipeline, step 2.3 has a condition set to run whenever the source branch is main.

If you queue a build on the main branch and cancel it while steps 2.1 or 2.2 are running, step 2.3 still runs, because eq(variables['Build.SourceBranch'], 'refs/heads/main') evaluates to true.

steps:
  - script: echo step 2.1
  - script: echo step 2.2; sleep 30
  - script: echo step 2.3
    condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')

Parameters in conditions

You can use parameters in conditions. Parameter expansion happens before the pipeline runs and replaces values surrounded by ${{ }} with the literal parameter values. Because parameter expansion occurs before condition evaluation, you can declare a parameter in a pipeline and embed the parameter inside any condition in that pipeline.

The condition in the following example combines two functions: succeeded() and ${{ eq(parameters.doThing, true) }}. The succeeded() function checks if the previous step succeeded. This function also returns true if there is no previous step.

The ${{ eq(parameters.doThing, true) }} function checks whether the doThing parameter is equal to true. The script step in the following example runs because there was no previous step and parameters.doThing is true by default.

parameters:
- name: doThing
  default: true
  type: boolean

steps:
- script: echo I did a thing
  condition: and(succeeded(), ${{ eq(parameters.doThing, true) }})

Template parameters in conditions

When you pass a parameter to a pipeline template, you can set the parameter's value in the template file or use templateContext to pass the parameter to the template.

The following parameters.yml template file declares the doThing parameter with a default value of true and uses the parameter in a job condition.

# parameters.yml
parameters:
- name: doThing
  default: true
  type: boolean

jobs:
  - job: B
    steps:
    - script: echo I did a thing
    condition: ${{ eq(parameters.doThing, true) }}

The following azure-pipelines.yml pipeline definition references the job in the parameters.yml template file. The output of the pipeline is I did a thing because the parameter doThing is true by default.

# azure-pipelines.yml

extends:
  template: parameters.yml

For more template parameter examples, see the Template usage reference.

FAQ

How can I trigger a job if a previous job succeeded with issues?

You can use the result of the previous job in a condition. In the following YAML, the condition eq(dependencies.A.result,'SucceededWithIssues') sets job B to run after job A succeeded with issues.

jobs:
- job: A
  steps:
  - script: echo Job A ran
- job: B
  dependsOn: A
  condition: eq(dependencies.A.result,'SucceededWithIssues') # targets the result of the previous job 
  steps:
  - script: echo Job A had issues

Why is my build still running after I canceled it?

You can experience this issue if a condition configured in a stage doesn't include a job status check function. To resolve the issue, add a job status check function to the condition. For more information, see Condition outcomes when a build is canceled.