---
title: DORA Metrics and Deployment Mapping
description: How CodeDD measures the five DORA delivery metrics, where deployment data comes from, and how to configure the production environment mapping
category: Portfolio Analytics
order: 4
---

# DORA Metrics and Deployment Mapping

CodeDD measures delivery performance with the five DORA metrics: **deployment frequency**, **lead time for changes**, **change fail rate**, **failed deployment recovery time**, and **deployment rework rate**. It reads them from your Git provider's own records — deployments, pipeline and workflow runs, release tags, pull requests, and issues — rather than from a survey.

Every figure depends on one question: **which events count as a production deploy?** The answer is the **deployment mapping**, a set of rules CodeDD applies to every repository in the portfolio. The defaults fit most repositories. This page explains what the rules are, how to check them, and how to change them in the **Production environment mapping** dialog.

**Where to find it:** open the portfolio organization dashboard, then **Development Activity**, then the **Delivery performance · DORA** section. Anyone who can open the dashboard can read it. Only organization **initiators** see the mapping dialog (the sliders icon) and the **Resync delivery metrics** button. Enterprise customers can also read the same figures through the DORA endpoint of the [Enterprise Data API](/documentation/enterprise-data-api).

## Quick start: check your mapping

Do this once per portfolio, and again when a team changes how it deploys.

1. Connect your Git provider under **Account → Profile → Git provider connections**, or import the repositories through a group audit. See [Setting Up a Portfolio Organization](/documentation/setting-up-a-portfolio-organization).
2. Open **Development Activity → Delivery performance · DORA**. The first sync reads the last 90 days of history.
3. In the repository table, read the **Evidence** column. Each repository shows which kind of evidence it is measured from: **Deployments**, **CI workflow**, **Release tags**, **Merges**, or **No deploy signal**.
4. For a repository with no deploy signal or a surprising deploy count, click the sliders icon to open **Production environment mapping**.
5. Read the **Classification preview**. It re-classifies the last 30 days of synced runs with your draft settings before you save, and shows how many production, hotfix, and rollback deploys each repository would have.
6. Click **Save configuration**. CodeDD recalculates with the new rules.

## What DORA measures

DORA (DevOps Research and Assessment) describes software delivery with two groups of metrics. **Throughput** says how fast changes reach production. **Instability** says how often those changes need fixing. CodeDD follows the 2024 DORA model with this split.

### The five metrics

| Metric | What CodeDD measures | Group |
|--------|----------------------|-------|
| **Deployment frequency** (DF) | How often production deployments happen. Rated per repository, then the median across repositories | Throughput |
| **Lead time for changes** | Time from a commit to the first production deployment that contains it | Throughput |
| **Failed deployment recovery time** (formerly MTTR, time to restore service) | Median time from a failed deployment to the deployment, or incident resolution, that restored service | Throughput |
| **Change fail rate** (change failure rate, CFR) | Share of planned production deployments that needed remediation | Instability |
| **Deployment rework rate** | Share of production deployments that are hotfixes or rollbacks | Instability |

The dashboard shows a 30-day window by default. Switch to **90 days** for a longer view. The weekly charts bucket the same deployments into ISO weeks, starting on the Monday on or before the window start.

### Tiers and the 2023-derived reference bands

CodeDD rates four metrics as **Elite**, **High**, **Medium**, or **Low**. The thresholds between tiers are fixed cut-points, called the **2023-derived reference bands**. Tooltips and the API methodology block name them wherever a tier appears. A value on a cut-point counts for the better tier.

| Metric | Elite | High | Medium | Low |
|--------|-------|------|--------|-----|
| Deployment frequency | At least 1 per day | At least 1 per week | At least 1 per month | Less often |
| Lead time for changes (median) | Up to 24 hours | Up to 7 days | Up to 30 days | Longer |
| Failed deployment recovery time (median) | Up to 1 hour | Up to 24 hours | Up to 7 days | Longer |
| Change fail rate | Up to 5% | Up to 10% | Up to 15% | Above 15% |

The bands rate one service. That is why deployment frequency across repositories is the median of the per-repository rates, not a sum: adding repositories does not lift the tier. The total across repositories (deploys per day) is shown beside it as volume, with no tier, and the trend charts plot it.

### Rework has reference buckets, not tiers

Deployment rework rate has no Elite or High label. The dashboard shades it against reference buckets from the 2025 DORA survey — up to 2%, up to 8%, up to 16%, above 16% — and shows no tier pill on its tile. When enough samples exist, its bucket still counts as an input to the instability factor of the composite tier.

### The composite tier

The **Composite tier** at the top of the section combines the rated metrics in two factors:

- **Throughput factor:** deployment frequency, lead time, and failed deployment recovery time.
- **Instability factor:** change fail rate and deployment rework rate.

Each tier gets an index: Low 0, Medium 1, High 2, Elite 3. The composite is the **floor of the mean index** of every rated metric. Each factor uses the same rule over its own metrics. The floor is deterministic and conservative: a mean of 2.9 is High, not Elite.

Example: deployment frequency High (2), lead time Elite (3), recovery Medium (1), change fail rate Elite (3), rework Medium (1). The sum is 10, the mean is 2, and the composite is **High**.

The composite needs deployment frequency to be rated and at least one instability metric (change fail rate or rework). A metric that is too thin to rate, or has no tier, stays out. Lead time measured by the merge-time proxy never counts.

### Minimum samples and confidence

Each metric needs a minimum number of samples before it shows a value or a tier. Confidence starts from the sample count: low below the minimum, medium from the minimum, high from the higher threshold.

| Metric | Sample unit | Value shown from | Tier from | Medium confidence from | High confidence from |
|--------|-------------|------------------|-----------|------------------------|----------------------|
| Deployment frequency | Deploys by the median repository | Any deploy | Any deploy | 3 | 30 |
| Lead time for changes | Deploy-linked changes | 5 | 5 | 5 | 15 |
| Change fail rate | Planned deployments | 5 | 20 | 5 | 20 |
| Failed deployment recovery time | Failure episodes | 3 | 3 | 3 | 10 |
| Deployment rework rate | Rework plus planned deployments | 5 | 5 | 5 | 20 |

Confidence is also capped when the data has gaps:

- **Partial window:** the window starts before a repository's synced history. Confidence is at most medium, and low when less than half the window was read.
- **Stale data:** a repository's last sync is more than nine days old. Confidence is at most medium.
- **Incomplete sync:** a resource is still being read or was cut short. Confidence is low.
- **PR merge proxy:** lead time falls back to merge time. Confidence is at most medium.

The badge on a metric tile names the main reason, for example **Partial window 12/30 days**, **Stale data**, **Incomplete sync**, **PR merge proxy**, **No failure signal**, **CI proxy**, **Release tags**, or **Merges as deploys**. Hover over the badge for the full explanation. A high-confidence metric with no data gaps shows no badge.

## Where the data comes from

CodeDD reads delivery data through your Git provider's API. It does not clone the repository for this and does not read application source code. The data arrives through a Git connection (OAuth or a personal access token) on GitHub, GitLab, Bitbucket, or Azure DevOps, and a scheduled sync keeps it current. The sections below cover the connection, the permissions each provider needs, what a sync reads, and how live data differs from an estimate.

### Git connections and permissions

DORA uses the same connections as audits: **GitHub**, **GitLab**, **Bitbucket**, and **Azure DevOps**, through OAuth or a personal access token (PAT). Connections belong to a person's CodeDD account, one per Git host. See [Repository Connection & Security](/documentation/repository-connection-security).

When a repository is imported with a person's connection, CodeDD records that connection as an **import grant** for that repository. The grant may power DORA for that repository only. The connect and import screens state that the connection is also used to read delivery metrics. Disconnecting, revoking the grant, or erasing the account removes it.

To sync a repository, CodeDD looks for a working credential in this order: the one that worked last time, an import grant, the owner of an active Portfolio Monitoring schedule, the group auditor, then other organization members who can read the repository.

### What each provider needs

DORA needs more than a clone-only token. It reads deployments, runs, pull requests, and issues.

| Provider | Permission DORA needs | Notes |
|----------|----------------------|-------|
| GitHub (GitHub App) | Read access to **Contents**, **Deployments**, **Issues**, and **Pull requests** | Add **Actions** read access when CI runs count as deploys. The dashboard asks you to approve updated permissions in Account Settings when one is missing |
| GitHub (OAuth or PAT) | `repo` scope; `public_repo` for public repositories only | Covers deployments, Actions, pull requests, and issues |
| GitLab | `read_api` | Covers deployments, environments, pipelines, merge requests, and issues |
| Bitbucket | `account`, `repository`, `pullrequest`, and `pipeline` | Bitbucket Cloud has no issue tracker, so no incidents are read from Bitbucket |
| Azure DevOps | `user_impersonation` | Covers environments, releases, builds, pull requests, and work items |

A missing permission shows a warning such as **missing permission** or **access denied** on the repository, and the banner **Additional Git permissions needed**. These are access problems. Changing the mapping does not fix them.

### What a sync reads and how often

Each sync reads three resources per repository: **pipelines** (deployments, CI runs, release tags), **pull requests**, and **issues**. A new repository backfills 90 days. A repository that gets a new deploy source also backfills 90 days for it.

The sync cadence follows the age of the organization's newest audit:

- **Daily** while the newest audit is up to 90 days old.
- **Weekly** from 90 to 365 days.
- **Paused** after 365 days, or when the organization is archived.
- **Daily regardless of audit age** while a [Portfolio Monitoring](/documentation/portfolio-monitoring) schedule is active.

Pausing stops new data. It never rewrites what was already measured. Initiators can also click **Resync delivery metrics** at any time.

The sync reads metadata: deployments and their statuses, run and pipeline records, tags, pull requests with their commits and changed paths, and issues. The one file it reads is a GitHub workflow file, and only when you pin a deploy workflow that another workflow triggers (`workflow_run`): CodeDD reads that file to find the build the deploy follows.

### Live, estimated, and snapshot data

Each repository carries a data-source label: **Live** (measured from synced records), **Mixed**, **Estimated**, **No activity** (connected, but nothing happened in the window), or **Connect Git** (no usable connection).

A repository without live data shows an audit-time **estimate**, apart from the measured tiles. The estimate is the unplanned commit share over the 90 days before the audit — a commit-message heuristic, not a rework rate. It never fills a measured tile, and deployment frequency has no estimate because it needs live sync.

## What counts as a production deploy

Deployment frequency, change fail rate, and recovery time all start from the list of production deploys, so this is the setting that matters most. CodeDD stores several kinds of deploy evidence side by side: deployment records, CI workflow runs, release tags, external CD statuses, and, for a repository that chooses it, merged pull requests. The kind that counts for a repository is its **deploy signal**. CodeDD decides it when the metrics are read, so switching the choice never needs a refetch, and one kind never erases another's history.

### The kinds of deploy evidence

| Evidence | What it is | Shown as |
|----------|------------|----------|
| **Deployment records** (native) | Records the Git host or deploy tool creates: GitHub Deployments, GitLab deployments, Bitbucket deployments, Azure DevOps environment deployments and releases | **Deployments** |
| **CI workflow runs** (pipeline fallback) | Successful GitHub Actions workflow runs, GitLab pipelines, Bitbucket pipelines, or Azure DevOps builds on a production ref | **CI workflow** |
| **Release tags** | Git tags and releases that match the production tag patterns | **Release tags** |
| **External CD statuses** | Check runs or commit statuses posted by a tool outside the Git host | Not offered in the dialog today |
| **Merged pull requests** (inferred) | Each pull request or merge request merged into a production branch, for a repository that deploys automatically on every merge and leaves no other record | **Merges** |

**Deployment records are the reference.** They name the environment the code went to, so CodeDD can tell staging from production. CI runs and tags usually carry no target of their own, so they need more rules to count. The kind that counts for a repository is its **deploy signal**, shown in the **Evidence** column.

### How Automatic chooses between them

**Automatic** is the default deploy signal for every repository. It keeps deployment records (also called native records) as the signal and adds another kind, CI workflow runs or release tags, only when the evidence supports it:

- If an enabled alternative ships **different commits** than the deployment records, both count, and a commit that both ship is counted once. This covers a monorepo service deployed by CI next to one deployed by a platform.
- If it ships **the same commits** and says it deploys to production (by environment, workflow name, pipeline name, or job name), it replaces the deployment records only when it ships more than **2 times** as many distinct commits over the last 90 days. This covers a repository whose deployment records exist only for occasional deploys.
- If it ships the same commits but does not say where to — for example a `Deploy` workflow that ships every merge to staging — only the deployment records count. If that workflow does deploy to production, pin it as the deploy workflow.

The choice is stored per repository and reviewed every 28 days or whenever the mapping changes. It does not flip from week to week when volumes hover near a threshold. The repository's row in the mapping dialog says why the current signal was chosen.

### Pinning one signal for a repository

In **What counts as a deploy**, each repository has a **Deploy signal** dropdown:

- **Automatic** — the rule above.
- **Deployment records** — GitHub Deployments, GitLab or Bitbucket deployments, Azure environments.
- **CI workflow** — each commit a chosen workflow builds successfully on a production branch. Pick the workflow or workflows under **Workflows that deploy**.
- **Release tags** — each release tag that matches the production tag patterns.
- **Merged pull requests** — each pull request merged into a production branch counts as a deploy. See [Repositories that deploy on every merge](#repositories-that-deploy-on-every-merge).

Pinning **CI workflow** switches the CI source on for that repository's provider even if the organization has not enabled it. Pinning a deploy workflow under Automatic does the same, because a pin says the workflow deploys.

Pin a source only if it has data: a repository pinned to a source with no records shows no deploys at all. The status line under the repository name says what counts and how many production deploys it found.

### Repositories that deploy on every merge

Some teams deploy production automatically as soon as a pull request merges into the default branch. No tag, release, or deployment record is created, and the deploy workflow is not recognisable as one. For such a repository the merge is the release. Choose **Merged pull requests** as its **Deploy signal**.

How it works:

- Each pull request (merge request on GitLab and Azure DevOps, pull request on Bitbucket) merged into a production branch becomes one production deploy at its merge time. A target branch counts when it is in **Production branches**, or is the default branch while **Default branch rule** treats it as production.
- Lead time for changes then runs from the pull request's first commit to its merge.
- It works for GitHub, GitLab, Azure DevOps, and Bitbucket, because CodeDD reads pull requests for all four.
- It is per repository only. There is no organization-wide switch, and **Automatic** never chooses it, because a merge alone cannot show that anything reached production.
- Changing the choice takes effect without a new download. CodeDD builds the deploys from the pull requests it already holds, and removes them again when you pick another signal.

**These deploys are inferred, not observed.** CodeDD cannot see whether a merge actually reached production, so the repository shows **Merges** in the Evidence column and the metrics carry a **Merges as deploys** badge that caps their confidence at medium. A push straight to the production branch, with no pull request, is not counted. If the repository has deployment records, CI workflow runs, or release tags, prefer those: they are observed.

### Deploys made by external tools

Tools such as Jenkins, CircleCI, Octopus, or Argo CD deploy without telling the Git host, so the host has no deployment record. There are three routes to get those deploys counted:

1. Have the tool write a deployment record to the Git host, through the GitHub Deployments API or the GitLab deployments API. It then counts as a deployment record.
2. If a workflow or pipeline on the production branch triggers or performs the deploy, pin that workflow as the deploy signal with **CI workflow**.
3. If you release by tagging, use **Release tags**.
4. If every merge to the production branch deploys on its own and nothing else records it, use **Merged pull requests**.

Another kind of evidence, commit statuses that a CD tool posts, exists in CodeDD's engine for Bitbucket but is not a choice in the dialog today. CodeDD does not read GitHub check runs or commit statuses as deploys yet.

## How production is recognised

Once the evidence kind is chosen, every run or record is judged by where it deployed or what it shipped. Deployment records are judged by their target environment. CI runs are judged by their branch or tag and then by a workflow gate. Release tags are judged by the tag name. The environment, branch, tag, and workflow rules below are the ones the mapping dialog lets you change.

### How environment names are read

Deployment records are judged by their target **environment**. CodeDD reads an environment name word by word, so separators and digits split it:

- Production words: `production`, `prod`, `prd`, `live`. `prod-eu`, `production east`, and `api-production` read as production.
- Stage words: `staging`, `stage`, `dev`, `development`, `test`, `qa`, `uat`, `integration`, `perf`, `e2e`, `preview`, `review`, `sandbox`, `demo`, `pre`, `non`, and per-pull-request names such as `pr-123`. `staging2`, `qa-prod`, `pre-production`, and `non-prod` read as non-production.
- Publish targets such as `pypi`, `npm`, `docker`, `pages`, `docs`, and `release` are not service deployments, unless the name also holds a production word (`docs-production`).
- Names that say nothing, such as a Heroku app name, a region, or a cluster, are **unclassified**. A deployment to one counts only when it is the repository's only environment. Otherwise the mapping dialog lists it under **Deployment environments** so you can decide.

**Never production** always wins. An exact name you list under **Production environments** beats the built-in names and any provider flag. A glob you list beats the built-in names too, but not a transient flag that a deploy tool set on purpose.

Disaster-recovery, canary, and blue/green slots whose names read as production are production targets too. A rollout to several targets of the same service counts once.

### Production branches and the default branch

CI runs count only when they ran on a production ref or deployed to a production environment. A production ref is a branch listed in **Production branches** (names or globs such as `release/*`), the repository's **default branch** while **Default branch is production** is on, or a production tag. A hotfix branch is not a production ref for CI runs unless you turn on **Count CI runs on hotfix branches as production deploys**.

### Which release tags count as production

A tag counts when it matches **Production release tags** (exact names) or **Production tag patterns**. The defaults match `v1.2` style and `release-1` style names. A prerelease tag such as `v2.0.0-rc.1`, `1.2.3-beta`, `v16.4.0-canary.61`, or `24.1b1` ships to testers, not production, so it never matches a pattern unless the pattern itself names prereleases. An exact production tag still counts.

### How CI runs pass the deploy gate

A CI run passes one gate before it counts as a deploy:

1. **Pins.** If the repository pinned deploy workflows, only pinned runs pass.
2. **Names.** A run whose own name targets something other than production is dropped: `Deploy to dev`, `Deploy (QA)`, `deploy_preview`, `Release to npm`, `Deploy Docs`. A name that also names production (`Deploy dev and prod`) stays. Release-note bots (`release-drafter`, `release notes`, `changelog`), preview and staging workflows, backports, and pull request bots are always dropped.
3. **Jobs.** Where CodeDD read the run's jobs, a run that has deploy jobs counts only if a job named for a production deploy succeeded. A green pipeline whose deploy job was skipped, manual, or gated shipped nothing.
4. **Workflow name patterns.** Otherwise the run's name must match the patterns. The defaults match `deploy`, `release`, `production`, `prod`, and `promote` style names. GitLab and Bitbucket run one pipeline per commit, so a pipeline with no name and no job names counts by its ref.

### What never counts as a production deploy

- Pull request and merge request runs, merge queue runs, and platform runs such as GitHub Pages or CodeQL.
- Scheduled runs and follow-up triggers such as `workflow_run` or Azure `buildCompletion`, unless the workflow is pinned as a deploy workflow. On GitHub, deployments created by a schedule, an issue bot, or an unpinned `workflow_run` never count.
- Deployments to staging, preview, review, QA, or temporary environments.
- Failed, cancelled, and skipped runs. A job stopped at its time limit is `cancelled` on GitHub Actions and is neutral.
- Teardown workflows such as `destroy`, `cleanup`, and `undeploy`.

## How deployments are counted

A deployment is the **first successful production deployment of a change set** in a repository. Several production records can describe one deployment: a rollout to two regions, a matrix of jobs, or a retry. CodeDD collapses them so that deployment frequency counts rollouts, not log lines.

### One rollout is one deployment

Regions, jobs, matrix legs, and retries of the same rollout collapse into one deployment. A production record counts only when it ships commits that are not yet in production — by its commit range when complete, otherwise by its head commit. CodeDD remembers 90 days of production history to decide this, so the tiles, the weekly chart, and the week detail all agree.

The metric tiles say how many deployments came from how many production records. Click a week on the **Weekly activity** chart to see every record and its role.

### Same-commit redeploys

A record for a commit that a target already had is part of the same rollout if the target received it less than **2 hours** before. Later, it is a **redeploy**. A redeploy is not a new deployment unless you turn on **Count same-commit redeploys**. Either way it appears in the week detail, labelled as counted or not counted.

### Failed attempts

A failed attempt never counts as a change failure. That includes a red pipeline, a failed deploy job that did not change production, and a failed run retried successfully on the same commit. CodeDD reports failed attempts and their retry time as a diagnostic only. CI runs and release tags keep one record per commit, so a CI run retried green does not show as a failed attempt.

### Rollbacks count as deployments

A detected rollback that takes its target back to an earlier change set counts as a deployment. It is also rework, and it marks the deployment before it as a failed change. See [Rollback detection](#rollback-detection).

## Lead time for changes

Lead time for changes answers: how long does a change take to reach production? It is a throughput metric. The tile shows the median and the 90th percentile (P90), and the tier compares the median with the lead-time bands: Elite up to 24 hours, High up to 7 days, Medium up to 30 days. A tier needs at least 5 changes linked to a production deployment.

### What one lead time sample is

The unit is a **commit**, measured from its author time to the first production deployment that contains it. Each deployment's commit range provides the commits. Where a range has no commit times — older stored rows, or a range linked by merge time — the unit is a pull request, measured from its first commit.

A pull request's first-commit time is the earliest author date among its commits. A rebase or rewritten branch keeps the older dates, so real lead time is not cut short.

### What lead time leaves out

These changes are excluded from lead time:

- Merge commits, and promotion pull requests such as `develop` to `main` that carry other pull requests' merges.
- Changes authored by automation: Dependabot, Renovate, and release-please branches, GitHub App bots, and GitLab project or group bots. An AI coding agent is not treated as a bot: someone asked for its change.
- Changes whose start falls after their deployment. That is a clock error, counted as a data gap, never as 0 hours.
- For a monorepo, pull requests that did not touch the deployed service's directories, when path scopes are set.

### The merge to deploy leg and the proxy

The lead-time data also splits out the **merge → deploy** leg: the time between merging a pull request and the deployment that ships it. It is part of the metric's data, not a separate tile. A long merge-to-deploy leg points at the release process rather than at coding or review.

With fewer than 5 deploy-linked samples, lead time falls back to **First commit → merge** (shown as **PR merge time (proxy)**, with a count such as **Deploy-linked 2/5**). The proxy has no tier and never counts toward the composite.

## Change failures and recovery

Change fail rate and failed deployment recovery time share one model: both count the same failure episodes. Deployment rework rate reads the same hotfix and rollback signals. A failed CI job or a red pipeline is not a change failure. A deployment that needed a hotfix, a rollback, or an incident response is. The sections below cover how an episode is found and how recovery is timed.

### Failure episodes

A **failure episode** is a planned production deployment that needed remediation. Three signals create one:

- The next deployment is a **hotfix** or **rollback** within **7 days**.
- A structural rollback takes the deploy chain back to code that production already ran.
- A **linked incident** points at the deployment.

**Change fail rate** is failed deployments divided by planned deployments. A deployment whose 7-day follow-up window is still open is left out until the window closes. The rate has no tier when no repository behind it has any failure signal source: a hotfix or rollback deploy seen, a deploy history CodeDD could compare, or an incident source. Without one, a 0% rate says nothing.

### Rollback detection

CodeDD reads rollbacks from the deploy chain, not only from words:

- A redeploy of an older commit: a deploy of a commit that production already ran.
- A deploy that the provider's compare puts behind the previous deploy. A deploy from another branch that moves forward is not a rollback.
- A deploy whose commit range carries a revert trailer naming a shipped commit.

It also reads names: a rollback workflow name, a rollback branch, GitHub's `revert-<n>-…` branches, a pull request title starting `Revert "` or `revert:`, and a body line `This reverts commit <sha>`. A revert of a revert lands the change again and is not rework. A rollback is charged to the deployment that shipped what was rolled back.

### Hotfix detection

A deployment is a hotfix from its own workflow name, its source branch, or the pull request that owns the deployed commit: a `hotfix:` title prefix, a `[hotfix]` tag, or a `hotfix` or `emergency` label. Text counts only in these anchored forms. "Add rollback to ledger" is planned work, and so is a branch named `feature/emergency-contact-form`. Other pull requests batched into the same deployment never make it rework.

### Incidents and the link window

An incident is an issue that carries one of the **Incident issue labels**, or whose title holds one of the **Incident keywords** as a whole word (`sev-1` matches in "sev-1 checkout down", not in "sev-10"). The issue body is not read. It links to the **last planned production deployment** of its repository or project in the **7 days** before it opened. That is the incident link window, configurable from 1 to 30 days. The linked deployment counts as a failed change, once, even if a hotfix also followed it. Bitbucket Cloud has no issue tracker, so it provides no incidents.

### Failed deployment recovery time

Failed deployment recovery time is the median time from the failed deployment to the deployment that restored service, or to the incident's resolution, whichever comes first. An episode counts in the window in which its restore falls.

## Repository roles

CodeDD detects each repository's **role** from the group audit's evidence: service, library, docs, infrastructure as code (IaC), or GitOps hub. The role decides whether the repository takes part in the deploy metrics: deployment frequency and the portfolio median, change fail rate, recovery time, and rework rate.

| Role | Takes part in the deploy metrics |
|------|----------------------------------|
| Service, GitOps hub, unknown | Measured as it is |
| Library or docs | Left out: these repositories publish packages or pages, they do not deploy |
| IaC | Measured only if it has its own counted production deployment |

An excluded repository shows **Does not deploy** with its role in the repository table, and lead time can still be measured from merges. Strong deploy evidence keeps a repository a service, and a production deploy found on a library surfaces as a suggestion to change its role. The mapping dialog has no role switch today. If CodeDD detected the wrong role, email [info@codedd.ai](mailto:info@codedd.ai) with the repository name.

## Monorepos and path scopes

A monorepo with several services deploying separately needs **path scopes**. Without them, every pull request merged between two deployments is linked to every deployment, so lead time and rework mix services.

In **What counts as a deploy**, each repository has a **Path scopes** box. Write one line per deploy target, then a colon, then the directories that target ships:

`production: services/api, libs/shared`

`api@*: services/api`

A target is a production environment, a deploy workflow (file path or name), a pipeline job or definition, or a release-tag series. A trailing `*` matches every target that starts with it. Directories are folders below the repository root; a trailing `/*` is allowed and dropped. A directory covers every file under it. Targets match regardless of case. A deployment that no target names is unscoped, and every pull request in its range counts for it.

When a pull request's changed files cannot be read, the scope fails open and the pull request counts. The count of these is recorded against lead time. A pull request that touches two services gives one sample per service.

## Production environment mapping settings

The **Production environment mapping** dialog holds every rule on this page. Open it with the sliders icon in the DORA section. From top to bottom it has: a note when a custom mapping applies, **What counts as a deploy** (per-repository signals), the **Classification preview**, **Production rules**, **Other deploy sources**, **Hotfix and rollback detection**, and **Incident classification**. The organization-wide rules apply to every repository in the portfolio, and a repository can add its own overrides.

### Who can change the mapping

Only organization **initiators** can change it, with the sliders icon on the DORA section. One mapping applies to every repository in the portfolio, and each repository can add overrides. See [Team Members & Access Roles](/documentation/team-members-access-roles).

The dialog is a draft until you click **Save configuration**. Another initiator's save replaces yours if it lands later, so agree on who edits the mapping. The change log below records each save.

### All settings at a glance

| Setting | Default | What it does |
|---------|---------|--------------|
| Production branches | `main`, `master` | Branches whose CI runs count as production. Names or globs |
| Default branch is production | On | Counts each repository's default branch as production besides the list |
| Production environments | `production`, `prod` | Environment names or globs that are production |
| Never production | Empty | Environment names or globs to ignore. Staging, preview, review, QA, and temporary environments are always ignored |
| Production release tags | Empty | Exact tag names that are production releases |
| Production tag patterns | `^v\d+\.\d+`, `^release-\d` | Patterns matched against git tags |
| Count same-commit redeploys | Off | Counts a redeploy of a commit already in production as a deployment |
| CI pipeline fallback | Off for every provider | Counts successful CI runs on production refs as deploys |
| Workflow name patterns | `deploy`, `release`, `production`, `prod`, `promote` | Workflow names that count as deploy workflows |
| Release tags as deploys | Off for every provider | Counts tags that match the production tag patterns as deploys |
| Hotfix branch patterns | `hotfix`, `hotfix/*`, `hotfix-*`, `emergency/*` and similar | Source branches that mark hotfix work |
| Hotfix workflow patterns | Names such as `hotfix`, `hot-patch`, `emergency fix`, `urgent fix`, `incident fix` | Workflow names that mark hotfix deploys |
| Rollback workflow patterns | `rollback`, `revert`, `redeploy previous` | Workflow names that mark rollback deploys |
| Rollback branch patterns | `revert-*`, `revert/*`, `rollback`, `rollback/*`, `rollback-*` | Source branches that mark rollback deploys |
| Count CI runs on hotfix branches as production deploys | Off | For teams that deploy the hotfix branch itself |
| Exclude rollback deploys from the CI job-failure diagnostic | On | Changes only the job-failure diagnostic, never a tile |
| Incident issue labels | `incident`, `outage`, `production-bug` | Issue labels that mark an incident |
| Incident keywords | `incident`, `sev-1`, `outage`, `hotfix` | Whole words in an issue title that mark an incident |
| Allow substring match on incident labels | Off | Matches a label that contains a configured label |
| Incident link window (days) | 7 | Days before an incident opened in which it links to a deployment. 1 to 30 |

### What counts as a deploy

This section lists each repository with its own controls. A row has the **Deploy signal** dropdown, **Workflows that deploy** for the CI source, a **Default branch rule**, and **Path scopes**.

The **Default branch rule** is **Default branch: organization rule**, **Default branch is production**, or **Default branch is not production**. Use the last one for a Gitflow repository whose default branch is `develop` and which ships to production from `master` or `release/*`. List those branches under **Production branches**.

Rows also show what CodeDD found: the CI workflows that ran on production branches with their deploys per day, and the deployment environments with how many deployments each had, marked as production or ignored. Use them to find the right workflow or environment name.

### Production rules

**Production branches** and **Production environments** take comma-separated names or globs. **Never production** takes names or globs to ignore. **Production release tags** takes exact tag names, and **Production tag patterns** takes patterns. **Count same-commit redeploys** is a switch.

### Other deploy sources

For repositories whose platform deploys without telling the Git host. Deployment records stay preferred.

- **CI pipeline fallback:** one checkbox per provider: GitHub Actions, GitLab CI pipelines, Bitbucket Pipelines, Azure DevOps builds. For GitHub it counts successful runs of workflows that match the workflow name patterns, on a production branch, pushed or started manually, once per commit. Pull request runs never count. GitHub needs Actions read access. CI evidence is shown with lower confidence than deployment records.
- **Workflow name patterns:** regular expressions matched against workflow names, ignoring case.
- **Release tags as deploys:** one checkbox per provider: GitHub releases and tags, GitLab tags, Bitbucket tags, Azure DevOps tags.

### Hotfix and rollback detection

This group decides which production deploys are corrective work. They feed rework and change fail rate, not deployment frequency. It holds **Hotfix branch patterns**, **Hotfix workflow patterns**, **Rollback workflow patterns**, **Rollback branch patterns**, and two switches.

Hotfix and rollback branch patterns also match the source branch of a pull request that later deploys from `main`. **Count CI runs on hotfix branches as production deploys** is for teams that deploy the hotfix branch itself. Off, a hotfix branch only marks the deploy that ships it as rework.

**Exclude rollback deploys from the CI job-failure diagnostic** does not change any tile. Change fail rate counts the deployment before a hotfix or rollback as failed whatever it says.

### Incident classification

This group sets the **Incident issue labels**, **Incident keywords**, **Allow substring match on incident labels**, and the **Incident link window (days)**. Incident settings change the tiles: they decide which deployments become failed changes and when recovery ends.

### Pattern syntax

Each field reads an entry without a prefix its own way. Prefix `re:` for a regular expression (regex) or `glob:` for a glob.

| Field | Entry without a prefix |
|-------|------------------------|
| Production branches, Production environments, Never production | Exact name or glob. Regular expressions are not allowed |
| Production tag patterns | Exact name; a glob if it holds `*` or `?`; otherwise a regular expression |
| Hotfix and rollback branch patterns | A plain name is the branch's leading path segment: `hotfix` matches `hotfix/1.2.3`, not `feature/remove-hotfix-flag` |
| Workflow name patterns (production, hotfix, rollback) | Regular expression, ignoring case |

Lists hold at most 50 entries of 256 characters. CodeDD refuses a pattern that could stall a worker (such as `(a+)+$`), one that is not a valid regular expression, one that uses lookarounds or backreferences, and a glob-looking entry that would silently be read as a regular expression. The error names the field and the entry.

### Classification preview

The preview re-classifies the last 30 days of synced runs with your draft and updates as you type. It shows:

- the headline: runs analysed, and how many are production, hotfix, and rollback deploys;
- a note when hotfix and rollback deploys will raise change fail rate;
- how many production deploys rest on the lower-confidence CI pipeline fallback;
- the count per match reason (environment, branch, default branch, tag, workflow);
- the top repositories with their production, hotfix, and rollback counts;
- under each pattern field, how many runs or tags each entry matched, with an example, or **No matches**.

Nothing in the preview is saved. Use it to confirm a pattern matches what you expect before you save.

## After you save a change

Saving the mapping starts three things: CodeDD recalculates the metrics with the new rules, it recomputes the audit snapshots that the change affects, and it records the change so that a reader can see the mapping was customised.

### Recalculation and regrading

CodeDD reclassifies the data it already has, so most changes need no new download. A repository that gets a new deploy source — CI runs or release tags — backfills 90 days for it. The dashboard shows **Applying new mapping…** until the recalculation finishes.

The newest group audit tracks live data. Older audits are **settled snapshots** of the numbers as of their audit date, and the dashboard says "Audit snapshot, no sync changes these numbers". A settled audit is recomputed **once** whenever its inputs change: a mapping save, a new version of CodeDD's classifier or metrics, a repository role change, or a backfill that reaches its window. When the headline moves, the snapshot is labelled **Regraded on** the date, and the values it replaced stay in its history. Nothing is overwritten silently.

### The Custom mapping label and the change log

When the organization's rules differ from CodeDD's defaults, or any repository pins its own deploy signal, the DORA section shows a **Custom mapping** chip. The dialog lists which organization rules changed, how many repositories pin their own signal, and when and where the last change happened. This tells a reader of the dashboard that the tiers were graded with a customised mapping.

Every save that changes a grading rule is logged with the account that saved it and the rules before and after. A save that changes nothing is not stored or re-applied.

## Configuration recipes

Five common setups, each starting from the default mapping. Use the **Classification preview** to check the result before you save.

### GitHub Actions deploys with no environment

The workflow deploys on every merge to `main`, for example to a platform that watches the repository. GitHub has no deployment record because the workflow declares no `environment:`. Under **What counts as a deploy**, set the repository to **CI workflow**, pick the deploy workflow, and save. Add `environment: production` to the workflow if you later want deployment records.

### Releases by tag

The team deploys when it pushes `v1.8.0`. Under **Other deploy sources**, check the provider under **Release tags as deploys**. Check that **Production tag patterns** matches your tag names, and read the preview counts under the field. For a repository, choose **Release tags** as its deploy signal.

### Gitflow with a develop default branch

Set the repository's **Default branch rule** to **Default branch is not production**. Add `master` and `release/*` to **Production branches**.

### A monorepo that deploys services separately

Give each deploy target its own line under **Path scopes**: `api-production: services/api`, `web-production: apps/web`. For services released by tag, use the tag series: `api@*: services/api`. Add a tag pattern that matches that series, such as `^api@\d`, and turn on **Release tags as deploys** for the provider.

### Environments with unusual names

For a deployment environment CodeDD marks as ignored or unclassified, such as `live-eu-west` or `heroku-acme-web`, add the name or a glob such as `heroku-acme-*` to **Production environments**. To keep a name out of production, add it to **Never production**. Listing an exact name beats CodeDD's built-in stage words, so a team that really ships to `stage-live` can say so.

## Troubleshooting

Most empty or surprising DORA figures come from one of a few causes. Find your symptom, then read the matching section.

| What you see | Most likely cause | First thing to do |
|--------------|-------------------|-------------------|
| "No production deploys detected in this window" | The deploy is not recognised as production, or access is missing | Check warnings, then the evidence kind, then environment names |
| "Connection expired - reconnect" | The provider token can no longer be renewed | Reconnect the provider in Account Settings |
| "Additional Git permissions needed" | The connection lacks a permission DORA reads | Approve the new permissions or reconnect |
| "Partial window 12/30 days" | Synced history is shorter than the window | Wait for the backfill |
| "PR merge time (proxy)" or no tier | Fewer samples than the minimum | Use the 90-day window or wait for more data |
| "No deploy signal" | No production deploy evidence for the enabled sources | Enable the right source or pin a workflow |
| "Does not deploy" | The repository role is library or docs | Confirm the role; if it is wrong, email support |

### No production deploys detected

The tile or repository shows "No production deploys detected in this window — check environment mapping." Work through the causes in this order:

1. **Access first.** A warning such as **missing permission** or **access denied** means CodeDD could not read deploy data. Fix the access, not the mapping.
2. **A workflow deploys but there is no deployment record.** The message "No deployment records, but CI workflow ran on the default branch" names the workflow. Choose it under **CI workflow** for that repository and save. Its workflows are read at the next sync, usually within a few minutes.
3. **An environment name is not recognised.** Check **Deployment environments** under the repository. Add the name to **Production environments**.
4. **Releases are tags.** Turn on **Release tags as deploys**, or pin **Release tags**. If the repository deploys on every merge and leaves no record at all, pin **Merged pull requests**.
5. **Production is not the default branch.** Set the default branch rule and list the real production branches.
6. **The repository does not deploy.** A library or docs repository is excluded by role.

### Connection expired

The banner **Connection expired - reconnect GitHub** means CodeDD can no longer renew the connection. The repositories on that host stop syncing. Reconnect the provider under **Account → Profile → Git provider connections**. The data already measured stays.

### Additional Git permissions needed

The provider connection works but lacks a permission DORA reads, such as GitHub Actions read access. For the GitHub App, approve the updated permissions named in the banner in Account Settings. For other providers, reconnect and grant the scopes in the permissions table above.

### Partial window

A label such as **Partial window 12/30 days** means the synced history covers 12 of the window's 30 days. The unread days are a coverage gap, not days without activity, and confidence is capped. It clears as the backfill completes, or for a new repository once it has enough history.

### Below the sample minimum

A metric with too few samples shows no value or no tier. Lead time shows **PR merge time (proxy)** and **Deploy-linked 2/5** until 5 deploy-linked changes exist. Change fail rate needs 20 planned deployments for a tier. Recovery time needs 3 failure episodes. Switch to the 90-day window to gather more samples.

### No deploy signal

The repository row says **No deploy signal**: no production deploy evidence was found for the evidence kinds that are enabled. Check that the right source is on, and that the repository has deployed recently. CI runs are not read until the CI source is enabled for the provider.

### Change fail rate has no tier

Either fewer than 20 planned deployments were measured, or no repository behind the figure has a failure signal source. A repository gets one by deploying hotfixes or rollbacks, having a comparable deploy history, or syncing issues that classify as incidents.

### Numbers did not change after saving

The dashboard shows **Applying new mapping…** while it recalculates. Reload the page after the label disappears. A repository with a new deploy source also waits for its 90-day backfill. If the figures still look wrong, reopen the dialog and check the **Classification preview** counts against what you expect.

### The save was refused

A message naming a field and a pattern means the pattern is invalid or unsafe: fix the entry. See the pattern syntax section for what each field accepts. A list holds at most 50 entries of 256 characters each.

### Numbers differ from another tool

Tools define a deployment, a change, and a failure differently. CodeDD counts a deployment once per change set, measures lead time per commit, and counts a failed attempt as a diagnostic, not a failure. Compare definitions before comparing figures.

## Frequently asked questions

Short answers to the questions people ask most about DORA metrics and deployment mapping in CodeDD. Each answer names the section that explains the topic in full.

### What is a production deploy in CodeDD?

A production deploy is a successful deployment record to a production environment, a successful CI run of a deploy workflow on a production branch or tag, or a release tag that matches the production tag patterns — whichever kind of evidence counts for that repository.

### Does CodeDD need access to my source code to compute DORA metrics?

No. DORA reads deployments, workflow and pipeline runs, tags, pull requests with their commit times, and issues through the provider API. It does not clone the repository for this and does not read application source code.

### Who can change the DORA mapping?

Organization initiators, using the sliders icon in the DORA section. Other members can read the metrics and see whether a custom mapping applies.

### How do I count releases made by tagging as deploys?

Check the provider under **Release tags as deploys**, check **Production tag patterns**, or pin **Release tags** as a repository's deploy signal. Prerelease tags such as `-rc.1` or `-beta` never count.

### How do I make GitHub Actions deploys count when no deployment record exists?

Pin **CI workflow** for the repository and pick the deploy workflow. Or enable **CI pipeline fallback** for GitHub in **Other deploy sources**. GitHub needs Actions read access.

### Why does my staging environment not count?

Names with stage words (`staging`, `qa`, `uat`, `dev`, `preview`, `pre-production`) are never production, even next to a production word. Add an exact name to **Production environments** if you really ship to it.

### Why does a library or documentation repository show "Does not deploy"?

CodeDD detected the role library or docs from the audit. Such repositories are left out of deployment frequency, change fail rate, recovery, rework, and the portfolio median.

### Why are redeploys of the same commit not counted?

One deployment is the first production deployment of a change set. A later record for the same commit is a redeploy. Turn on **Count same-commit redeploys** to count them.

### What is the difference between a hotfix and a rollback?

A hotfix is corrective work shipped quickly, found by branch, workflow name, title prefix, or label. A rollback takes production back to earlier code, found from the deploy chain, a rollback workflow or branch, or a revert. Both count as rework and make the deployment before them a failed change.

### How does an incident affect change fail rate?

An incident links to the last planned production deployment of its repository or project in the 7 days before it opened. That deployment counts as a failed change, once. The link window is configurable from 1 to 30 days.

### Why does lead time show "PR merge time (proxy)"?

Fewer than 5 changes could be linked to a production deployment. The proxy shows first commit to merge, has no tier, and is not used in the composite tier.

### How is the composite tier calculated?

CodeDD takes the tier index of each rated metric (Low 0, Medium 1, High 2, Elite 3) and uses the floor of the mean. It needs deployment frequency plus at least one instability metric. See [The composite tier](#the-composite-tier).

### Why is there no tier on deployment rework rate?

Rework has reference buckets from the 2025 DORA survey, not Elite and High bands. The tile shows the value without a tier pill.

### Will changing the mapping rewrite old audits?

A saved mapping change recomputes each settled audit once. Audits whose headline changes are labelled **Regraded on** the date, and the replaced values are kept in the history.

### How do I handle a monorepo?

Set **Path scopes** for the repository so each deploy target keeps only the pull requests that touch its directories. See [Monorepos and path scopes](#monorepos-and-path-scopes).

### Which Git permissions does DORA need?

GitHub App: read Contents, Deployments, Issues, and Pull requests, plus Actions when CI runs count. GitLab: `read_api`. Bitbucket: `account`, `repository`, `pullrequest`, `pipeline`. Azure DevOps: `user_impersonation`. See [What each provider needs](#what-each-provider-needs).

### How often does the data refresh?

Daily while the organization's newest audit is under 90 days old or a Portfolio Monitoring schedule is active, weekly up to 365 days, then paused. Initiators can also click **Resync delivery metrics**.

## Related documentation

- [Portfolio Monitoring](/documentation/portfolio-monitoring)
- [Setting Up a Portfolio Organization](/documentation/setting-up-a-portfolio-organization)
- [Repository Connection & Security](/documentation/repository-connection-security)
- [Team Members & Access Roles](/documentation/team-members-access-roles)
- [Enterprise Data API](/documentation/enterprise-data-api)
- [AI Agent Telemetry (OpenTelemetry Setup)](/documentation/ai-agent-telemetry-opentelemetry-setup)
