---
name: "Azure DevOps conventions"
description: "Always-on conventions for work item links, branch names, commit messages, pull requests and pipeline YAML in Azure DevOps repositories."
applyTo: "**"
---

# Azure DevOps conventions

Traceability is the point: every change should lead back to the work item that asked for it, and every release should be assemblable from the repository alone. Facts below were verified 2026-09 against learn.microsoft.com/azure/devops.

## Work item links

- Put the work item ID in the branch name **and** in the commit or pull request text. A branch created from the work item is linked automatically; anything else has to say the ID.
- **Azure Repos:** `#123` in a commit message or PR description creates the link (repository setting *Commit mention work item resolution*, on by default). In the PR description, typing `#` opens the work item picker.
- **GitHub repositories connected to Azure Boards:** use `AB#123` instead — in the commit message, PR description or issue description. `AB#` in a PR *title* or a comment does not create a link.
- **State transitions:** `fix`, `fixes` and `fixed` (case-insensitive) before the ID resolve the item — `Fixes #123` — when the commit reaches the default branch by push, or when a PR completes with **Complete associated work items after merging**. Squash merges are excluded from that PR path, so squash-merging teams link the item on the PR instead of relying on the keyword.
- One keyword per item: `Fixes #123 and fixes #124` resolves both; `fixes #123,124` resolves only 123. Never use a keyword for an item the change does not actually finish.

## Branch names

- `<type>/<work-item-id>-<short-slug>`, lower case, hyphens, no spaces: `feature/4711-picking-list-csv`, `bugfix/4802-null-order-total`, `hotfix/4900-payment-timeout`.
- Types: `feature`, `bugfix`, `hotfix`, `release`, `spike`, `chore`; personal scratch work goes under `users/<alias>/<topic>`. Delete the source branch when the PR completes; a long-lived branch other than the default and `release/*` needs a documented reason.

## Commit messages

- Subject: imperative, ≤ 72 characters, no trailing period — `Add CSV export to the order detail page`. Body explains *why*, wrapped at 72.
- Reference the work item on its own line at the end: `Related to #4711`, or `Fixes #4711` when the commit completes it.
- One logical change per commit; formatting-only and generated-file churn goes in its own commit so the diff stays reviewable. Never put secrets, tokens, connection strings or personal data in a message — history is permanent.

## Pull requests

- Title: imperative and readable on its own, with the work item at the end — `Export picking list as CSV (#4711)`. No ticket noise inside the sentence.
- Description states what changed and why, how it was tested, and anything a deployer must do (migrations, configuration, feature flags). Link the work item in the description, not only in the title.
- Keep PRs under roughly 400 changed lines of hand-written code; split larger work by workflow step so each PR ships something reviewable. Open as **Draft** while it is not ready, and set auto-complete rather than merging past a failing required check.

## Pipeline YAML

- Pipeline definitions live in the repository (`azure-pipelines.yml` at the root, extra pipelines under `.azuredevops/pipelines/`), never edited as a Classic UI definition.
- Declare triggers explicitly; the implicit "every branch" default surprises people:

  ```yaml
  trigger:
    branches:
      include: [main, release/*]
  pr:
    branches:
      include: [main]
  ```

- Pin task major versions (`- task: UseDotNet@2`), name every stage, job and step, and keep shared steps in templates under `.azuredevops/templates/` rather than copied between pipelines.
- Keep credentials out of the repository: use variable groups linked to Azure Key Vault, and map a secret into a step explicitly, because secrets are not added to the environment automatically:

  ```yaml
  - script: ./deploy.sh
    env:
      API_KEY: $(apiKey)
  ```

- Check out with the narrowest rights the job needs — `persistCredentials: true` leaves a usable token in the work folder:

  ```yaml
  - checkout: self
    fetchDepth: 1
    persistCredentials: false
  ```

- A pipeline that builds a PR from a fork must not receive secrets; keep deployment stages behind an environment with approvals.

Go deeper: `backlog-writing` for the work items themselves, `security-code-review` for reviewing pipeline YAML and secrets, and the `azure-devops` MCP server item for reading this data from an agent.
