Skip to content

Stop the localization pipeline from opening duplicate PRs - #4615

Merged
priyankatiwari08 merged 3 commits into
mainfrom
dev/automation/dedupe-localization-pr
Sep 2, 2026
Merged

priyankatiwari08 merged 3 commits into
mainfrom
dev/automation/dedupe-localization-pr

Conversation

@priyankatiwari08

@priyankatiwari08 priyankatiwari08 commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor

Problem

The Localization-CI pipeline has opened four byte-for-byte identical pull requests — #4607, #4612, #4613 and #4614 — one per night, each the same +2/-2 change across the same 13 Strings.*.resx files.

Root cause

Ours, not the localization team's or OneLocBuild's.

Localization-CI is a classic (designer) ADO pipeline, so its logic isn't in this repo. Its last step, "Open PR on GitHub", is inline PowerShell that builds a timestamped branch and unconditionally POSTs a new PR:

$timestamp  = Get-Date -Format "yyyyMMdd-HHmmss"
$branchName = "dev/automation/onelocbuild-$timestamp"

It never checks whether a PR is already open. Its only guard is git diff --cached --quiet against main, which stays non-empty until a previous PR merges — so every run passes the guard and opens another PR.

Two things keep it firing: the schedule is daily at 19:30 UTC with scheduleOnlyWithChanges: false, and the ADO-side locfiles/* PR is never auto-completed, so master never receives the translations and the same delta regenerates each day.

Fix

The logic moves into a reusable, testable script the pipeline step calls — the pattern already used by eng/pipelines/scripts/Sync-GitHubToAdo.ps1.

eng/pipelines/scripts/Open-LocalizationPr.ps1:

  • Stable branch (dev/automation/onelocbuild), rebuilt from base each run — no timestamp proliferation.
  • Exits before any push or API call when the resources match the base branch.
  • Reuses the open PR (PATCH to refresh it) instead of opening a second one.
  • Pushes with --force-with-lease pinned to the SHA read earlier in the run, so an overlapping run fails loudly instead of discarding another run's result.
  • Authenticates via GIT_CONFIG_* extraheader, so the token never reaches .git/config, a command line, or an exception message.
  • -DryRun walks the full decision tree without publishing anything.

17 Pester tests in eng/pipelines/scripts/tests/, git and Invoke-RestMethod mocked.

Validation

Pipeline, dry-run. Localization-CI was pointed at this branch with LocPrDryRun=true. Build 171252 (manual) and 171295 (scheduled) both succeeded, loaded all 13 files, and short-circuited on No localization changes relative to 'main'. The scheduled run created zero PRs — first clean night in six.

Fork, live. Those runs never touch the write paths, so the script was run three times against priyankatiwari08/SqlClient with real credentials and no -DryRun:

Run Input Behaviour PRs after
1 Delta present Pushed → created PR 1
2 Identical re-run Skipped push → reused PR #1 1
3 New delta Pushed → reused PR #1 1

Three runs, one PR. The old step produces three.

Credentials. The GIT_CONFIG_* change landed after those runs, so it was checked separately with git ls-remote: valid token resolves the ref, invalid token fails with Authentication failed. The second case matters — it fails on a public repo, proving the header is really being sent rather than the request succeeding anonymously.

Merge sequence

Order matters; flipping first makes the step fail, since the script won't be on main yet.

  1. Merge this PR.
  2. In Localization-CI, set GitHubBranch → main and LocPrDryRun → false.
  3. Watch the next 19:30 UTC run.

Follow-up (AB#47729)

  • Consider scheduleOnlyWithChanges: true or a weekly cadence.
  • Auto-complete the ADO locfiles/* PR so master receives translations and the delta stops regenerating.
  • Port Localization-CI to YAML under eng/pipelines/ so it gets code-reviewed like the rest of CI.

Checklist

  • Tests added or updated — 17 Pester tests, two pipeline runs, a three-run fork test
  • Public API changes documented — n/a
  • Verified against customer repro — n/a; verified against the four duplicate PRs
  • No breaking changes — CI infrastructure only, no shipping code touched

The scheduled Localization-CI pipeline opens a brand-new GitHub PR on
every run because its inline "Open PR on GitHub" step pushes a
timestamped branch (dev/automation/onelocbuild-<yyyyMMdd-HHmmss>) and
never checks whether an equivalent PR is already open. Four
byte-for-byte identical PRs (#4607, #4612, #4613, #4614) accumulated as
a result.

Add eng/pipelines/scripts/Open-LocalizationPr.ps1 as a reusable,
idempotent replacement for that inline step:

- Uses a stable branch name, rebuilt from the base branch each run, so
  no timestamped branch proliferation and no commit accumulation.
- Exits without pushing or calling the GitHub API when the localized
  resources are identical to the base branch.
- Skips the force-push when the remote branch already holds the exact
  same tree on top of the same base.
- Reuses an already-open pull request (PATCH) instead of opening a
  second one, and otherwise opens exactly one new PR.

Includes Pester v5 tests covering the de-duplication contract with git
and Invoke-RestMethod mocked.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cb6c5b07-fb77-43bc-a4aa-a9d39aa04438
Copilot AI balanced review requested due to automatic review settings August 31, 2026 11:32
@priyankatiwari08
priyankatiwari08 requested a review from a team as a code owner August 31, 2026 11:32
@github-project-automation github-project-automation Bot moved this to To triage in SqlClient Board Aug 31, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds a reusable localization PR publisher to prevent duplicate scheduled pull requests.

Changes:

  • Reuses a stable branch and existing open PR.
  • Avoids unnecessary commits and pushes.
  • Adds Pester coverage and test instructions.
File summaries
File Description
eng/pipelines/scripts/Open-LocalizationPr.ps1 Implements localization PR de-duplication.
eng/pipelines/scripts/tests/Open-LocalizationPr.Tests.ps1 Tests validation and de-duplication behavior.
eng/pipelines/scripts/tests/README.md Documents running pipeline-script tests.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 2
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread eng/pipelines/scripts/Open-LocalizationPr.ps1 Outdated
Comment thread eng/pipelines/scripts/Open-LocalizationPr.ps1
Allows validating the pipeline wiring (paths, token scopes, OneLocBuild
output, existing-PR lookup) from a feature branch without pushing a
branch or creating/updating a pull request in the public repository.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cb6c5b07-fb77-43bc-a4aa-a9d39aa04438
Copilot AI review requested due to automatic review settings August 31, 2026 12:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (2)

eng/pipelines/scripts/Open-LocalizationPr.ps1:274

  • WorkingDirectory is caller-controlled, but an existing value is recursively deleted before cloning. Passing an agent workspace or source directory would destroy unrelated files. Only remove directories created by this script; Git can use an existing empty caller-provided directory and will reject a non-empty one without deleting it.
    if (Test-Path -LiteralPath $WorkingDirectory) {
        Remove-Item -LiteralPath $WorkingDirectory -Recurse -Force
    }

eng/pipelines/scripts/Open-LocalizationPr.ps1:279

  • Embedding the access token in the clone URL persists it as remote.origin.url in .git/config. When -WorkingDirectory is supplied, that directory is deliberately retained, leaving the credential in plaintext; a clone failure can also echo the URL through Invoke-Git's exception. Use transient authentication for clone/fetch/push, keep the configured origin credential-free, and redact command arguments in errors.
    # The token is embedded in the remote URL so git can push without an
    # interactive credential prompt. Git masks it in its own output, and the
    # pipeline masks the secret in task logs.
    $cloneUrl = "https://x-access-token:$AccessToken@github.com/$repoSlug.git"
  • Files reviewed: 3/3 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread eng/pipelines/scripts/Open-LocalizationPr.ps1
- Authenticate git via GIT_CONFIG_* environment config instead of embedding
  the token in the remote URL, so it never reaches .git/config, a process
  command line, or Invoke-Git exception messages. Cleared in the finally block.
- Read GitHub error bodies from $_.ErrorDetails first, since PowerShell 7
  exposes an HttpResponseMessage with no GetResponseStream(); keep the stream
  path as a Windows PowerShell fallback so 4xx bodies are no longer dropped.
- Replace the unconditional force-push with --force-with-lease pinned to the
  remote SHA observed earlier in the run, so an overlapping run fails instead
  of discarding a concurrent localization result.

Adds 4 tests (17 total, all passing).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cb6c5b07-fb77-43bc-a4aa-a9d39aa04438
Copilot AI review requested due to automatic review settings September 1, 2026 08:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Review details

Suppressed comments (2)

Previously missed (2) — in code that hasn't changed since the last review.

eng/pipelines/scripts/Open-LocalizationPr.ps1:246

  • A relative -SourceDirectory passes this validation, but $sourceResources remains relative. After Set-Location switches into the clone, the later Copy-Item resolves it from the clone and fails to find the OneLocBuild output. Resolve the source path while still in the caller's directory.
$sourceResources = Join-Path $SourceDirectory $ResourcesPath

eng/pipelines/scripts/Open-LocalizationPr.ps1:260

  • A caller-supplied relative -WorkingDirectory is cloned relative to the original location, but after Set-Location the same relative value is used again for $targetResources, producing <clone>/<WorkingDirectory>/<ResourcesPath> and failing. Convert the working directory to a provider-resolved absolute path before changing location.
$ownedWorkingDirectory = $false
if ([string]::IsNullOrWhiteSpace($WorkingDirectory)) {
    $WorkingDirectory = Join-Path ([System.IO.Path]::GetTempPath()) "loc-pr-$([guid]::NewGuid().ToString('n'))"
    $ownedWorkingDirectory = $true
}
  • Files reviewed: 3/3 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@paulmedynski paulmedynski moved this from To triage to In review in SqlClient Board Sep 1, 2026
@paulmedynski paulmedynski added the Area\Engineering Use this for issues that are targeted for changes in the 'eng' folder or build systems. label Sep 1, 2026
@priyankatiwari08
priyankatiwari08 merged commit ef2ca70 into main Sep 2, 2026
270 of 310 checks passed
@github-project-automation github-project-automation Bot moved this from In review to Done in SqlClient Board Sep 2, 2026
@priyankatiwari08
priyankatiwari08 deleted the dev/automation/dedupe-localization-pr branch September 2, 2026 08:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area\Engineering Use this for issues that are targeted for changes in the 'eng' folder or build systems.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants