> For the complete documentation index, see [llms.txt](https://developer.harness.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.harness.io/harness-platform/3.0/in-harness-3.0/pipelines/steps.md).

# Steps

A Step is the smallest executable unit within a pipeline stage. Steps are the building blocks that perform actual work; running scripts, invoking actions, requesting approvals, or managing background services. Harness 3.0 provides multiple step types with a flexible, short-form YAML syntax.

### Step types <a href="#step-types" id="step-types"></a>

| Step Type  | Key          | Description                                   |
| ---------- | ------------ | --------------------------------------------- |
| Run        | `run`        | Execute shell commands                        |
| Run-Test   | `run-test`   | Execute tests with intelligence and splitting |
| Action     | `action`     | Invoke actions and plugins                    |
| Clone      | `clone`      | Clone a repository                            |
| Approval   | `approval`   | Request human or automated approval           |
| Background | `background` | Start long-running services                   |
| Barrier    | `barrier`    | Synchronize parallel stages                   |
| Group      | `group`      | Sequential substeps                           |
| Parallel   | `parallel`   | Concurrent substeps                           |
| Queue      | `queue`      | Queue management                              |
| Template   | `template`   | Reference a reusable template                 |

### Common step properties <a href="#common-step-properties" id="common-step-properties"></a>

All step types share these common properties in addition to their type-specific fields.

```typescript
interface StepCommon {
  id: string                              // Step identifier
  name: string                            // Display name
  if: string                              // Conditional execution
  disabled: boolean                       // Disable step
  timeout: string                         // Max execution time
  needs: string | string[]                // Step dependencies
  strategy: Strategy                      // Matrix/looping
  status: StatusConfig                    // Status configuration
  on-failure: FailureStrategy             // Failure handling
  delegate: DelegateSelector              // Delegate selector
  env: Record<string, string>             // Environment variables (GHA compat)
}
```

### Run step <a href="#run-step" id="run-step"></a>

The Run step executes shell commands. It supports multiple syntax variants from single-line shorthand to fully configured forms with containers, environment variables, and test reports.

```typescript
// Short form: a single string
type StepRunShort = string

// Long form
interface StepRun {
  shell: "sh" | "bash" | "powershell" | "pwsh" | "python"
  script: string | string[]              // Command script(s)
  container: string | {                  // Container image
    image: string
    connector: string                    // Registry connector
    credentials: { username: string; password: string }
    pull: "always" | "never" | "if-not-exists"
    entrypoint: string | string[]
    args: string | string[]
    env: Record<string, string>
    privileged: boolean
    user: string | number
    group: string | number
    cpu: string | number
    memory: string | number              // e.g., "1gb", "512m"
    volumes: Mount[]
    ports: string[]
    network: string
  }
  env: Record<string, string>            // Environment variables
  report: Report | Report[]              // Test report paths
}
```

#### Shortest syntax <a href="#shortest-syntax" id="shortest-syntax"></a>

```yaml
steps:
  - run: echo "Hello"
```

#### Named step <a href="#named-step" id="named-step"></a>

```yaml
steps:
  - name: install
    run: npm install
```

#### Multi-line script <a href="#multi-line-script" id="multi-line-script"></a>

```yaml
steps:
  - name: setup-and-build
    run: |
      echo "Setting up environment..."
      export BUILD_DATE=$(date +%Y-%m-%d)
      npm ci --production
      npm run build
      echo "Build completed at $BUILD_DATE"
```

#### Array of commands <a href="#array-of-commands" id="array-of-commands"></a>

Each command runs independently. If one fails, subsequent commands are skipped.

```yaml
steps:
  - name: build-steps
    run:
      - npm ci
      - npm run lint
      - npm run build
      - npm test
```

#### With container <a href="#with-container" id="with-container"></a>

Run the step inside a specific container image. Supports both a short-form string and a long-form object with pull policy, credentials, and resource limits.

```yaml
# Short form: image string <a href="#short-form-image-string" id="short-form-image-string"></a>
steps:
  - name: build
    run:
      script: go build ./...
      container: golang:1.23-alpine

# Long form: full container configuration <a href="#long-form-full-container-configuration" id="long-form-full-container-configuration"></a>
  - name: test-with-config
    run:
      script: pytest tests/
      container:
        image: python:3.12
        pull: always
        credentials:
          username: ${{ secrets.DOCKER_USER }}
          password: ${{ secrets.DOCKER_PASS }}
        memory: 1gb
        cpu: 2
```

#### With environment variables <a href="#with-environment-variables" id="with-environment-variables"></a>

```yaml
steps:
  - name: deploy
    run:
      script: ./deploy.sh
      env:
        AWS_REGION: us-east-1
        DEPLOY_ENV: production
        API_KEY: ${{ secrets.API_KEY }}
        BUILD_NUMBER: ${{ pipeline.sequenceId }}
```

#### With shell selection <a href="#with-shell-selection" id="with-shell-selection"></a>

Supported values: `sh`, `bash`, `powershell`, `pwsh`, `python`.

```yaml
steps:
  - name: bash-script
    run:
      shell: bash
      script: |
        set -euo pipefail
        echo "Running with bash"

  - name: python-script
    run:
      shell: python
      script: |
        import os
        print(f"Python version: {os.sys.version}")
        result = 2 + 2
        print(f"Result: {result}")

  - name: powershell-script
    run:
      shell: powershell
      script: |
        Write-Host "Running on Windows"
        Get-Process | Select-Object -First 5
```

#### With test reports <a href="#with-test-reports" id="with-test-reports"></a>

```yaml
steps:
  - name: test
    run:
      script: npm test -- --coverage
      report:
        type: junit
        path:
          - "coverage/junit.xml"
          - "coverage/report-*.xml"
```

### Run-test step <a href="#run-test-step" id="run-test-step"></a>

The Run-Test step extends the Run step with built-in test intelligence, test splitting, and test report collection. Harness analyzes test results, identifies flaky tests, and optimizes execution through intelligent parallelism.

```typescript
interface StepTest {
  shell: "sh" | "bash" | "powershell" | "pwsh" | "python"
  script: string | string[]
  match: string | string[]               // Test file patterns
  container: string | ContainerConfig
  env: Record<string, string>
  splitting: {                            // Test splitting
    disabled: boolean
    concurrency: number                   // Parallel splits
  }
  intelligence: {                         // Test intelligence
    disabled: boolean
  }
  report: Report | Report[]
}
```

#### Basic test step <a href="#basic-test-step" id="basic-test-step"></a>

```yaml
steps:
  - name: unit-tests
    run-test:
      script: npm test
      report:
        type: junit
        path:
          - "coverage/junit.xml"
```

#### Test Intelligence <a href="#test-intelligence" id="test-intelligence"></a>

Harness Test Intelligence selects only the tests relevant to code changes, significantly reducing execution time.

```yaml
steps:
  - name: smart-tests
    run-test:
      script: mvn test
      intelligence:
        disabled: false
      report:
        type: junit
        path:
          - "target/surefire-reports/*.xml"
```

#### Test splitting <a href="#test-splitting" id="test-splitting"></a>

Distribute tests across parallel instances for faster execution.

```yaml
steps:
  - name: parallel-tests
    run-test:
      script: npx jest --shard=${{ split.index }}/${{ split.total }}
      match:
        - "src/**/*.test.ts"
      splitting:
        concurrency: 4
      report:
        type: junit
        path:
          - "coverage/junit-*.xml"
```

### Action step <a href="#action-step" id="action-step"></a>

Action steps invoke pre-built integrations and plugins. Harness 3.0 supports GitHub Actions (via `uses:`), Harness plugins, and Drone plugins.

```typescript
interface StepAction {
  uses: string                            // Action reference
  with: Record<string, any>              // Action inputs
  env: Record<string, string>            // Environment variables
  report: Report | Report[]
}
```

#### GitHub Action <a href="#github-action" id="github-action"></a>

```yaml
steps:
  - action:
      uses: actions/checkout@v4

  - action:
      uses: actions/setup-node@v4
      with:
        node-version: "20"
        cache: npm

  - run: npm ci
  - run: npm test
```

#### Action with inputs <a href="#action-with-inputs" id="action-with-inputs"></a>

```yaml
steps:
  - name: upload-artifact
    action:
      uses: actions/upload-artifact@v4
      with:
        name: build-output
        path: dist/
        retention-days: 5
```

#### Harness plugin <a href="#harness-plugin" id="harness-plugin"></a>

```yaml
steps:
  - name: build-and-push
    action:
      uses: docker-build-push
      with:
        registry: docker.io
        repo: my-org/my-app
        tags:
          - latest
          - ${{ pipeline.sequenceId }}
        dockerfile: Dockerfile
        context: .
        username: ${{ secrets.DOCKER_USER }}
        password: ${{ secrets.DOCKER_PASS }}
```

{% hint style="info" %}
**GITHUB ACTIONS COMPATIBILITY**

Harness 3.0 supports most GitHub Actions out of the box. Actions execute inside containers with the appropriate runtime. Some Actions that depend on GitHub-specific APIs may require configuration adjustments.
{% endhint %}

### Approval step <a href="#approval-step" id="approval-step"></a>

Approval steps pause pipeline execution and wait for human or automated approval. Harness 3.0 supports native Harness approvals, Jira-based approvals, and ServiceNow-based approvals via the `uses:` field.

```typescript
interface StepApproval {
  uses: string                            // Approval system (harness, jira, servicenow)
  with: Record<string, any>              // Configuration
  env: Record<string, string>
}
```

#### Harness Approval <a href="#harness-approval" id="harness-approval"></a>

```yaml
steps:
  - name: approve-production
    approval:
      uses: harness
      with:
        approvers:
          users:
            - admin@company.com
            - devops-lead@company.com
          groups:
            - production-approvers
          minimum: 1
        message: |
          Production deployment for version ${{ inputs.version }}.
          Please review and approve.
        timeout: 4h
```

#### Jira Approval <a href="#jira-approval" id="jira-approval"></a>

```yaml
steps:
  - name: jira-approval
    approval:
      uses: jira
      with:
        connector: jira-connector
        project: DEPLOY
        issue_type: "Change Request"
        status: Approved
        fields:
          Summary: "Deploy ${{ inputs.version }} to production"
          Description: "Automated deployment request"
        timeout: 24h
```

#### ServiceNow Approval <a href="#servicenow-approval" id="servicenow-approval"></a>

```yaml
steps:
  - name: snow-approval
    approval:
      uses: servicenow
      with:
        connector: snow-connector
        ticket_type: change_request
        fields:
          short_description: "Production deployment"
          category: Software
          priority: "3 - Moderate"
        approval_criteria:
          status: Approved
        timeout: 48h
```

### Background step <a href="#background-step" id="background-step"></a>

Background steps start long-running services that remain active for the duration of the stage. They share the same structure as Run steps but use the `background:` key. Typical uses include databases, caches, and local dev servers needed for integration testing.

#### Redis service <a href="#redis-service" id="redis-service"></a>

```yaml
steps:
  - name: redis
    background:
      container: redis:7-alpine

  - name: run-tests
    run:
      script: npm test
      env:
        REDIS_URL: redis://redis:6379
```

#### PostgreSQL service <a href="#postgresql-service" id="postgresql-service"></a>

```yaml
steps:
  - name: postgres
    background:
      script: docker-entrypoint.sh postgres
      container:
        image: postgres:16-alpine
        ports:
          - "5432:5432"
      env:
        POSTGRES_DB: testdb
        POSTGRES_USER: testuser
        POSTGRES_PASSWORD: testpass

  - name: run-migrations
    run:
      script: npm run db:migrate
      env:
        DATABASE_URL: postgres://testuser:testpass@postgres:5432/testdb

  - name: run-tests
    run:
      script: npm test
      env:
        DATABASE_URL: postgres://testuser:testpass@postgres:5432/testdb
```

#### Local dev server <a href="#local-dev-server" id="local-dev-server"></a>

```yaml
steps:
  - name: app-server
    background:
      script: npm start
      container:
        image: node:20-alpine
        ports:
          - "3000:3000"

  - name: e2e-tests
    run:
      script: npx cypress run
      env:
        BASE_URL: http://app-server:3000
```

{% hint style="warning" %}
**READINESS PROBES**

Background services may take time to initialize. Use readiness probes or add an explicit wait step to ensure services are fully available before subsequent steps begin. Without verification, dependent steps may encounter connection failures.
{% endhint %}

### Barrier step <a href="#barrier-step" id="barrier-step"></a>

Barrier steps synchronize execution across parallel stages. When a barrier is reached, the stage pauses until all other stages referencing the same barrier name also reach it. Barrier names must be declared in the `pipeline.barriers` list.

```typescript
interface StepBarrier {
  name: string                            // Barrier name (must match pipeline.barriers)
}
```

#### Synchronizing parallel stages <a href="#synchronizing-parallel-stages" id="synchronizing-parallel-stages"></a>

```yaml
pipeline:
  barriers:
    - deployment-sync
  stages:
    - parallel:
        stages:
          - name: deploy-service-a
            steps:
              - run: ./deploy.sh service-a
              - barrier:
                  name: deployment-sync
              - run: ./verify.sh service-a
          - name: deploy-service-b
            steps:
              - run: ./deploy.sh service-b
              - barrier:
                  name: deployment-sync
              - run: ./verify.sh service-b
          - name: deploy-service-c
            steps:
              - run: ./deploy.sh service-c
              - barrier:
                  name: deployment-sync
              - run: ./verify.sh service-c
```

{% hint style="info" %}
**BARRIER SCOPE**

Barriers are scoped to the current pipeline execution. All parallel stages referencing the same barrier name will wait for each other. If a stage fails before reaching the barrier, the barrier times out after the specified duration.
{% endhint %}

### Clone step <a href="#clone-step" id="clone-step"></a>

The Clone step checks out source code from a repository. By default, Harness automatically clones the pipeline repository, but the Clone step allows full customization of depth, submodules, sparse checkout, and more.

```typescript
interface StepClone {
  repo: string                            // Repository name
  connector: string                       // Repository connector
  clean: boolean                          // Run git clean/reset
  depth: number                           // Clone depth
  disabled: boolean                       // Disable clone
  filter: string                          // Partial clone filter
  insecure: boolean                       // Skip SSL
  lfs: boolean                            // Clone LFS files
  path: string                            // Workspace path
  "set-safe-directory": boolean           // git safe.directory
  "sparse-checkout": string               // Sparse checkout patterns
  strategy: "source-branch" | "merge"     // Clone strategy
  submodules: boolean                     // Clone submodules
  tags: boolean                           // Clone tags
  trace: boolean                          // Enable trace
  ref: string | {                         // Branch/tag/SHA
    name: string
    type: "branch" | "pull-request" | "tag"
    sha: string
  }
}
```

| Property          | Type               | Description                                |
| ----------------- | ------------------ | ------------------------------------------ |
| `repo`            | `string`           | Repository name to clone                   |
| `depth`           | `number`           | Clone depth (0 for full history)           |
| `submodules`      | `boolean`          | Initialize and clone submodules            |
| `lfs`             | `boolean`          | Fetch Git LFS files                        |
| `sparse-checkout` | `string`           | Sparse checkout patterns                   |
| `strategy`        | `string`           | `source-branch` or `merge`                 |
| `ref`             | `string \| object` | Branch, tag, SHA, or structured ref object |

#### Shallow clone <a href="#shallow-clone" id="shallow-clone"></a>

```yaml
steps:
  - clone:
      depth: 1
```

#### With submodules <a href="#with-submodules" id="with-submodules"></a>

```yaml
steps:
  - clone:
      depth: 50
      submodules: true
      tags: true
```

#### Clone a specific repository <a href="#clone-a-specific-repository" id="clone-a-specific-repository"></a>

```yaml
steps:
  - name: clone-shared-lib
    clone:
      repo: my-org/shared-library
      connector: github-connector
      path: ./libs/shared
      depth: 1
```

#### PR clone ref <a href="#pr-clone-ref" id="pr-clone-ref"></a>

```yaml
steps:
  - clone:
      ref:
        name: feature/new-api
        type: pull-request
        sha: abc123def
      strategy: merge
      depth: 10
```

#### Sparse checkout <a href="#sparse-checkout" id="sparse-checkout"></a>

Clone only specific directories from a large monorepo.

```yaml
steps:
  - clone:
      sparse-checkout: |
        services/api/
        packages/shared/
        configs/
      depth: 1
```

{% hint style="info" %}
**DEFAULT CLONE BEHAVIOR**

If no Clone step is defined and `clone: disabled` is not set at the stage or pipeline level, Harness automatically clones the pipeline repository with default settings.
{% endhint %}

### Group & parallel steps <a href="#group-and-parallel-steps" id="group-and-parallel-steps"></a>

Steps can be organized into sequential groups or run in parallel within a stage. Both `group:` and `parallel:` accept nested step lists and support conditionals, failure strategies, and other common step properties.

#### Sequential group <a href="#sequential-group" id="sequential-group"></a>

```yaml
steps:
  - name: build-and-test
    group:
      steps:
        - run: npm ci
        - run: npm run build
        - run: npm test

  - name: deploy
    if: ${{ trigger.branch }} == "main"
    group:
      steps:
        - run: ./deploy.sh staging
        - run: ./verify.sh staging
```

#### Parallel steps <a href="#parallel-steps" id="parallel-steps"></a>

All parallel steps must complete before the next step begins.

```yaml
steps:
  - run: npm ci

  - parallel:
      steps:
        - name: lint
          run: npm run lint
        - name: typecheck
          run: npm run typecheck
        - name: unit-tests
          run: npm test

  - run: npm run build
```

#### Group with failure strategy <a href="#group-with-failure-strategy" id="group-with-failure-strategy"></a>

```yaml
steps:
  - name: optional-checks
    on-failure: ignore
    group:
      steps:
        - name: lint
          run: npm run lint
        - name: audit
          run: npm audit
```

{% hint style="warning" %}
**PARALLEL STEP ISOLATION**

Parallel steps share the same filesystem within a stage but execute concurrently. Be careful with steps that write to the same files. If isolation is needed, consider using parallel stages instead.
{% endhint %}

### Template step <a href="#template-step" id="template-step"></a>

Template steps reference reusable step templates stored in the Harness template library. The `uses:` field follows the pattern `account.name@version`.

```typescript
interface StepTemplate {
  uses: string                            // Template ref (account.name[@version])
  with: Record<string, any>              // Input parameters
  env: Record<string, string>
}
```

#### Basic template reference <a href="#basic-template-reference" id="basic-template-reference"></a>

```yaml
steps:
  - name: deploy
    template:
      uses: account.deploy-to-k8s@1.0.0
      with:
        namespace: production
        replicas: 3
        image: my-app:${{ pipeline.sequenceId }}
```

#### Template with version pinning <a href="#template-with-version-pinning" id="template-with-version-pinning"></a>

```yaml
steps:
  - name: scan
    template:
      uses: account.security-scan@1.5.2
      with:
        scan_type: full
        severity_threshold: high
        fail_on_critical: true

  - name: publish
    template:
      uses: account.publish-artifact@3.0.0
      with:
        registry: docker.io
        repo: my-org/my-app
        tag: ${{ inputs.version }}
```

#### Template with inputs and env <a href="#template-with-inputs-and-env" id="template-with-inputs-and-env"></a>

```yaml
steps:
  - name: notify
    template:
      uses: account.slack-notify@2.1.0
      with:
        channel: "#deployments"
        message: "Deployed ${{ inputs.version }}"
      env:
        SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }}
```

{% hint style="warning" %}
**TEMPLATE VERSIONING**

Always pin template versions in production pipelines. Using `latest` or omitting the version may cause unexpected behavior when the template is updated. Use semantic versioning (e.g., `account.my-template@2.1.0`) for reproducible builds.
{% endhint %}
