> 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/harness-platform-resources/variables-and-expressions/branch-build-sequence-id.md).

# Branch-scoped build numbers

Harness provides branch-scoped build numbers that increment independently for each branch in your repository. Use the `<+pipeline.branchSeqId>` expression to version artifacts and track builds per branch, instead of relying on the global pipeline sequence counter, `<+pipeline.sequenceId>`, that increments across all branches.

{% hint style="info" %}
This feature is behind the feature flag `CI_ENABLE_BRANCH_SEQUENCE_ID`. Contact [Harness Support](mailto:support@harness.io) to enable the feature.
{% endhint %}

***

### What you will learn in this topic <a href="#what-you-will-learn-in-this-topic" id="what-you-will-learn-in-this-topic"></a>

By the end of this topic, you will be able to:

* Use the `<+pipeline.branchSeqId>` expression to track incremental build numbers per branch.
* Understand when the branch sequence ID is available and when it returns null.
* Apply branch-scoped build numbers to version artifacts and Docker images.
* Manage branch sequence counters using the Harness API.

***

### Before you begin <a href="#before-you-begin" id="before-you-begin"></a>

Before you use branch-scoped build numbers, ensure you have the following:

* The `CI_ENABLE_BRANCH_SEQUENCE_ID` feature flag enabled for your account. Contact [Harness Support](mailto:support@harness.io) to enable it.
* Pipeline builds triggered with branch context (webhook triggers or manual branch builds).
* A [Harness API key](/harness-platform/3.0/harness-platform-resources/automation/api/add-and-manage-api-keys.md) if you plan to manage branch sequences via API.

***

### Overview <a href="#overview" id="overview"></a>

The global `<+pipeline.sequenceId>` increments with every execution across all branches, making it difficult to identify which build number corresponds to a specific branch.

The branch sequence ID (`<+pipeline.branchSeqId>`) solves this by maintaining separate counters for each branch-repository combination:

| Branch    | Repository          | Execution | `sequenceId` | `branchSeqId` |
| --------- | ------------------- | --------- | ------------ | ------------- |
| main      | github.com/org/repo | 1st       | 1            | 1             |
| feature-x | github.com/org/repo | 2nd       | 2            | 1             |
| main      | github.com/org/repo | 3rd       | 3            | 2             |
| feature-x | github.com/org/repo | 4th       | 4            | 2             |

### How it works <a href="#how-it-works" id="how-it-works"></a>

The branch sequence ID is:

1. **Scoped to pipeline + branch + repository**: Each unique combination of pipeline identifier, normalized branch name, and normalized repository URL maintains its own counter.
2. **Incremented at execution start**: The counter increments when the pipeline execution begins, before any steps run. If a build is queued due to concurrency limits, the sequence is assigned when the build is first triggered, not when it starts executing from the queue.
3. **Normalized for consistency**: Repository URLs are normalized (e.g., `https://github.com/org/repo.git` and `git@github.com:org/repo` resolve to the same identifier). Branch names have `refs/heads/` prefixes stripped.
4. **Persisted in database**: Counters persist across pipeline executions and survive service restarts.
5. **Cleaned up on pipeline deletion**: When a pipeline is deleted, all associated branch sequence records are removed.

#### When the expression resolves <a href="#when-the-expression-resolves" id="when-the-expression-resolves"></a>

The `<+pipeline.branchSeqId>` expression returns a value when:

* Build is triggered by a **PR webhook** (uses the source branch)
* Build is triggered by a **push webhook** to a branch
* Build is a **manual branch build**

The expression returns `null` when:

* Build is triggered by a **tag push** (tags don't have branch context)
* Build is a **manual tag build**
* Build targets a **specific commit SHA** without branch context
* The feature flag is not enabled

#### Pipelines with codebase disabled <a href="#pipelines-with-codebase-disabled" id="pipelines-with-codebase-disabled"></a>

The branch sequence ID works even when **Clone Codebase** is disabled in your pipeline's codebase configuration. Harness extracts branch and repository information directly from the webhook trigger payload. This way, the sequence counter increments correctly regardless of whether the pipeline clones the repository.

This is useful for pipelines that:

* Use a custom clone step instead of the built-in Clone Codebase
* Only need metadata from the trigger (branch name, PR number) without cloning
* Clone repositories conditionally based on pipeline logic

### Expression reference <a href="#expression-reference" id="expression-reference"></a>

| Expression                | Description                                                                                                             |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `<+pipeline.branchSeqId>` | Returns the incremental sequence ID for the current branch and repository combination. Returns `null` if not available. |

### Examples <a href="#examples" id="examples"></a>

#### Display build information <a href="#display-build-information" id="display-build-information"></a>

```yaml
- step:
    type: Run
    name: Build Info
    identifier: build_info
    spec:
      shell: Sh
      command: |
        echo "Pipeline: <+pipeline.name>"
        echo "Global Build #: <+pipeline.sequenceId>"
        echo "Branch: <+codebase.branch>"
        echo "Branch Build #: <+pipeline.branchSeqId>"
```

Example output for the 5th build on the `develop` branch (47th overall pipeline execution):

```
Pipeline: my-ci-pipeline
Global Build #: 47
Branch: develop
Branch Build #: 5
```

#### Version artifacts by branch <a href="#version-artifacts-by-branch" id="version-artifacts-by-branch"></a>

This example creates Docker image tags that include the branch-specific build number:

```yaml
- step:
    type: BuildAndPushDockerRegistry
    name: Build Image
    identifier: build_image
    spec:
      connectorRef: dockerhub
      repo: myorg/myapp
      tags:
        - <+codebase.branch>-<+pipeline.branchSeqId>
        - <+codebase.branch>-latest
```

This produces tags like:

* `develop-1`, `develop-2`, `develop-3` for builds on `develop`
* `feature-auth-1`, `feature-auth-2` for builds on `feature-auth`

#### Semantic versioning with branch builds <a href="#semantic-versioning-with-branch-builds" id="semantic-versioning-with-branch-builds"></a>

Combine with other expressions for semantic versioning:

```yaml
- step:
    type: Run
    name: Set Version
    identifier: set_version
    spec:
      shell: Sh
      command: |
        BRANCH="<+codebase.branch>"
        BUILD_NUM="<+pipeline.branchSeqId>"

        if [[ "$BRANCH" == "main" ]]; then
          VERSION="1.0.${BUILD_NUM}"
        elif [[ "$BRANCH" == release/* ]]; then
          RELEASE_VER="${BRANCH#release/}"
          VERSION="${RELEASE_VER}.${BUILD_NUM}"
        else
          # Feature branches
          SAFE_BRANCH=$(echo "$BRANCH" | tr '/' '-')
          VERSION="0.0.0-${SAFE_BRANCH}.${BUILD_NUM}"
        fi

        echo "Version: $VERSION"
      outputVariables:
        - name: VERSION
```

#### Conditional logic for non-branch builds <a href="#conditional-logic-for-non-branch-builds" id="conditional-logic-for-non-branch-builds"></a>

This example describes how to handle cases where the expression returns `null`, such as tag builds or commit-only builds:

```yaml
- step:
    type: Run
    name: Generate Version
    identifier: generate_version
    spec:
      shell: Sh
      command: |
        BRANCH_SEQ="<+pipeline.branchSeqId>"

        # When branchSeqId is null, the expression resolves to "null" string
        if [[ -n "$BRANCH_SEQ" && "$BRANCH_SEQ" != "null" ]]; then
          echo "Branch build #${BRANCH_SEQ}"
          VERSION="<+codebase.branch>-${BRANCH_SEQ}"
        else
          echo "Non-branch build (tag or commit)"
          VERSION="<+codebase.tag>-<+pipeline.sequenceId>"
        fi

        echo "Using version: $VERSION"
```

### Manage branch sequences <a href="#manage-branch-sequences" id="manage-branch-sequences"></a>

{% hint style="info" %}
These API endpoints require the `CI_ENABLE_BRANCH_SEQUENCE_ID` feature flag. If the flag is not enabled, the API returns a message prompting you to enable it.
{% endhint %}

#### View branch sequences <a href="#view-branch-sequences" id="view-branch-sequences"></a>

You can view all branch sequence records for a pipeline using the API:

```bash
curl -X GET \
  'https://app.harness.io/pipeline/api/pipelines/<PIPELINE_ID>/branch-sequences?accountIdentifier=<ACCOUNT_ID>&orgIdentifier=<ORG_ID>&projectIdentifier=<PROJECT_ID>' \
  -H 'x-api-key: <API_KEY>'
```

Response:

```json
{
  "data": [
    {
      "normalizedRepoUrl": "github.com/myorg/myrepo",
      "branch": "main",
      "sequenceId": 42,
      "createdAt": 1709123456789,
      "lastUpdatedAt": 1709234567890
    },
    {
      "normalizedRepoUrl": "github.com/myorg/myrepo",
      "branch": "develop",
      "sequenceId": 15,
      "createdAt": 1709123456789,
      "lastUpdatedAt": 1709134567890
    }
  ]
}
```

#### Reset a branch sequence <a href="#reset-a-branch-sequence" id="reset-a-branch-sequence"></a>

To reset a branch sequence counter, delete the record and it will restart from 1 on the next build:

```bash
curl -X DELETE \
  'https://app.harness.io/pipeline/api/pipelines/<PIPELINE_ID>/branch-sequences?accountIdentifier=<ACCOUNT_ID>&orgIdentifier=<ORG_ID>&projectIdentifier=<PROJECT_ID>&repoUrl=<REPO_URL>&branch=<BRANCH>' \
  -H 'x-api-key: <API_KEY>'
```

#### Set a specific sequence value <a href="#set-a-specific-sequence-value" id="set-a-specific-sequence-value"></a>

You can set the sequence to a specific value (useful for migrations):

```bash
curl -X PUT \
  'https://app.harness.io/pipeline/api/pipelines/<PIPELINE_ID>/branch-sequences/set?accountIdentifier=<ACCOUNT_ID>&orgIdentifier=<ORG_ID>&projectIdentifier=<PROJECT_ID>&repoUrl=<REPO_URL>&branch=<BRANCH>&sequenceId=100' \
  -H 'x-api-key: <API_KEY>'
```

### Comparison with other expressions <a href="#comparison-with-other-expressions" id="comparison-with-other-expressions"></a>

| Expression                | Scope                    | Increments                  | Use case                        |
| ------------------------- | ------------------------ | --------------------------- | ------------------------------- |
| `<+pipeline.sequenceId>`  | Pipeline (global)        | Every execution             | Unique execution identifier     |
| `<+pipeline.branchSeqId>` | Pipeline + Branch + Repo | Per branch/repo combination | Branch-specific build numbering |
| `<+pipeline.executionId>` | Global (UUID)            | N/A (unique ID)             | Execution URL, logging          |

### Troubleshooting <a href="#troubleshooting" id="troubleshooting"></a>

#### Expression returns null <a href="#expression-returns-null" id="expression-returns-null"></a>

If `<+pipeline.branchSeqId>` returns `null` when you expect a value:

1. **Verify feature flag**: Confirm `CI_ENABLE_BRANCH_SEQUENCE_ID` is enabled for your account.
2. **Check build type**: Tag builds and commit SHA builds don't have branch context.
3. **Check webhook payload**: For webhook triggers, verify the payload includes branch information.

#### Different branches showing same sequence <a href="#different-branches-showing-same-sequence" id="different-branches-showing-same-sequence"></a>

Branch sequences are scoped by normalized repository URL. If you're seeing unexpected behavior:

1. **Check repository URL normalization**: URLs like `https://github.com/org/repo.git` and `git@github.com:org/repo` normalize to the same identifier.
2. **Verify branch names**: Branch names are normalized (e.g., `refs/heads/main` becomes `main`).

#### Sequence not incrementing <a href="#sequence-not-incrementing" id="sequence-not-incrementing"></a>

1. **Check for errors in execution logs**: Look for warnings about branch sequence increment failures.
2. **Verify database connectivity**: The sequence counter is stored in the database.
3. **Check pipeline identity**: Different pipelines have completely independent counters.

### Related articles <a href="#related-articles" id="related-articles"></a>

* [Use Harness expressions](/harness-platform/3.0/harness-platform-resources/variables-and-expressions/harness-variables.md): Learn how to reference variables, expressions, and built-in pipeline data in your workflows.
* [Harness expressions reference](/harness-platform/3.0/harness-platform-resources/variables-and-expressions/harness-expressions-reference.md): Browse the complete list of available expressions including pipeline, stage, and environment variables.
* [Pipeline triggers](/harness-platform/3.0/harness-platform-resources/triggers/triggering-pipelines.md): Configure webhook triggers for push events, pull requests, and manual branch builds that provide branch context.
