Note 39 · Azure

CI Gates: What Do You Actually Gate in Azure DevSecOps Pipelines?

Argue for native CI linters and language test gates so locale and string regressions fail the build before release does.

21 August 2026 9 min read

Key takeaways

  • Language and string hygiene are enforced in CI, not only in review folklore.
  • CI-native linters are mandatory; local hooks are optional mirrors.
  • Gates cover UI strings and prompt templates in scope packages.
  • Forbidden hardcoded tokens in core fail the build with an owned exception process.
  • Keep the active gate list short, documented, and mapped to real defects.
  • In-scope locale failures are release blockers, not translation tickets alone.

Locale bugs and copy regressions show up late, not in polite Slack reminders. Shared code makes it worse: a forbidden hardcoded token in core, a hard-coded English-only prompt template, a missing translation key that only fails in one locale.

Put language gates in CI. Native linters in the pipeline beat optional pre-commit hooks for shared repos. Gates should cover prompt templates and UI strings. Fail fast on forbidden hardcoded tokens in core packages. Keep the gate list short enough that people do not bypass it. Locale and copy regressions are product bugs, not translation tickets filed after the fact.

Named model: CI-native language gates

  • Recurring defect: the same string class escapes review (stray token, missing key, single-language template). Reviewers acting as human greps are the signal, not the control.
  • Named linter rule: forbidden pattern, package scope, and a one-line fix hint. A wiki admonition without a failing check is decoration.
  • CI on every PR: native pipeline tasks are the source of truth. Local hooks may mirror for speed; they are not the contract.
  • Contract: if a class of string defect is recurring, it is a build gate, not a culture speech. Folklore does not merge-block. Builds do.

Three panels: recurring defect, named linter rule, and CI on every PR as the merge gate

Figure 1. Recurring string defects become named rules. CI fails the PR. Hooks can mirror; they do not replace unwilling enforcement at merge time.

Context and constraint

A common failure: the same class of defect escapes review: a hardcoded token in a shared package, a string key missing for one locale, a prompt template that assumes a single language. Reviewers get tired of being human greps. CI greps do not get tired.

Pre-commit helps willing developers. Shared repos need unwilling enforcement at merge time. Native linters and small language tests catch forbidden tokens, missing keys, and template mistakes before release trains move. Folklore in code review does not scale across teams.

Figure 2 is the Learn framing for Azure Pipelines as the place unwilling enforcement actually runs.

Azure Pipelines overview showing CI as a shared pipeline surface

Figure 2. Locale and copy regressions are product bugs. Put the gates in CI where unwilling enforcement actually runs. Source: Microsoft Learn: what is Azure Pipelines.

Figure 3 is the task surface. Native linters and short language tests beat optional hooks that disappear under release pressure.

Azure Pipelines task control options for gating builds

Figure 3. Native linters and short language tests beat optional hooks that disappear under release pressure. Source: Microsoft Learn: pipeline tasks.

Design

1. Promote recurring review comments into linters

When reviewers keep asking for the same string hygiene, write a rule. Rules should name the forbidden pattern and the fix. A wiki admonition without a failing check is decoration.

2. Prefer CI-native over optional hooks

Optional pre-commit is skipped under pressure. CI on every PR is the contract. Local hooks can mirror CI for speed, but CI is source of truth. "Works on my laptop" is not a language strategy.

3. Cover UI strings and prompt templates

Products ship language in two places: the interface and the prompts. Gates that only scan UI code miss the template that greets users in the wrong tongue. Include pack files in the same policy. Figure 4 is that dual scope.

Two scopes: UI strings and prompt templates under the same CI policy, with a UI-only anti-pattern

Figure 4. Gate the interface and the templates. UI-only i18n checks leave prompt packs free to greet in the wrong language.

4. Fail on forbidden hardcoded tokens in core

Variant codes, asset paths, and display-name literals in core packages are boundary defects. Maintain a deny list and fail the build. Exceptions need an explicit allow file with owners, not comments that say temporary forever. Figure 5 is the package-boundary contract.

Core packages on a deny list, variant and sample packs allowed variant-specific or demo language, owned allow file for exceptions

Figure 5. Core stays generic. Variant and sample packs may carry variant-specific or demo language. Exceptions live in an owned allow file.

5. Keep the gate list short

Five sharp gates beat twenty noisy ones. Noise trains bypass. Each gate should map to a defect you have actually shipped. Retire gates that no longer earn their keep.

6. Treat locale failures as product bugs

Missing keys and broken plurals are release blockers when that locale is in scope. Do not file them as "translation follow-ups" if the feature already claims support. Honesty in scope beats optimistic checkboxes.

What a minimal gate set looks like

Start with: forbidden hardcoded tokens in core; required i18n keys for touched features; prompt template language metadata present; no raw user-facing English literals in flagged packages when i18n is required. Add screenshot or snapshot tests only where they catch real bugs. Resist Unicode academic perfectionism on day one. Ship the gates that match your last three incidents.

Making failures actionable

A failing language gate should print the file, the rule, and a one-line fix hint. "See docs" without a path is how people disable checks. Link to the template guide or the i18n README from the error output. On-call and PR authors share the same message. If fixing requires tribal knowledge, the gate is unfinished.

Boundary hygiene and i18n are the same discipline

Both ask whether core stayed generic. A variant-specific string in core is a boundary leak. A hard-coded locale assumption is a locale leak. One CI policy can serve both if you organize rules by package boundary: core versus variant pack versus sample pack. Samples may contain demo language; core may not. Say that in the rule names.

Failure modes

Reviewer as linter. Humans miss strings on Fridays.

Hooks only. Pressure skips local tools.

UI-only i18n checks. Prompts drift freely.

Deny list without owners. Exceptions accumulate in silence.

Gate sprawl. Engineers force-merge to survive.

Locale as backlog forever. Locales are "supported" in slides only.

What to copy / what not to copy

Copy

  • Language and string hygiene are enforced in CI, not only in review folklore.
  • CI-native linters are mandatory; local hooks are optional mirrors.
  • Gates cover UI strings and prompt templates in scope packages.
  • Forbidden hardcoded tokens in core fail the build with an owned exception process.
  • The active gate list stays short, documented, and mapped to real defects.
  • In-scope locale failures are release blockers, not translation tickets alone.

Do not copy blindly

Gates slow the first PRs that trip them and speed every PR after. Short gate lists require product judgment; long lists require bypass culture. Scanning prompts adds paths to maintain; not scanning prompts adds release risk. Deny lists need curation; without them shared cores rot. Treating locale as product quality may delay a launch date you should not have claimed.

Primary references: your pipeline docs (Azure Pipelines or GitHub Actions) and i18n tooling docs for the stack you run. Pair gate names with the package-boundary ADR so failures point at architecture, not only at strings.

Lint in CI. Cover prompts and UI. Ban hardcoded tokens in core. Keep gates few and sharp. Call locale breaks what they are: product bugs. Folklore does not merge-block. Builds do.