diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2df7ddab928..028c28ca318 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -997,12 +997,15 @@ jobs: # now means Linux + macOS + the gates; Windows re-enters the gate when the # tracked failures are fixed, not before. # - # Dispatch-only proved too late. #6639, #6556, #6605 and #6606 each landed with - # green pull-request CI while breaking Windows, and the breakage surfaced days - # later on a manual dispatch. So a pull request whose changes job reports - # windows=true (a Windows path, or an added src/tests line naming a Windows - # marker; see scripts/ci/windows-sensitive-diff.sh) now runs the nine shards, - # and the daily schedule catches whatever that filter misses. + # Pull requests never run these shards (AGENTS.md "CI policy", a maintainer + # decision): Windows runs on the nightly schedule and on a lane=all dispatch. + # The changes job still computes windows=true for a Windows-sensitive pull + # request (a Windows path, or an added src/tests line naming a Windows marker; + # see scripts/ci/windows-sensitive-diff.sh). That verdict does not schedule + # these jobs; it posts a non-blocking notice, and a maintainer dispatches + # lane=all on the branch before merging. #6639, #6556, #6605, #6606 and #7468 + # each landed with green pull-request CI while breaking Windows, which is why + # that dispatch is part of merging a Windows-sensitive pull request. platform-windows: name: windows ${{ matrix.shard }}/9 needs: [changes, select-windows-runner] diff --git a/scripts/ci/windows-sensitive-diff.sh b/scripts/ci/windows-sensitive-diff.sh index 05bcaaba27e..e642788ccba 100644 --- a/scripts/ci/windows-sensitive-diff.sh +++ b/scripts/ci/windows-sensitive-diff.sh @@ -26,6 +26,14 @@ selected="${PATH_SELECTED:-}" emit() { printf 'windows=%s\n' "$1" >> "$GITHUB_OUTPUT" printf 'windows-sensitive: %s (%s)\n' "$1" "$2" + # Pull requests never run the Windows shards (AGENTS.md CI policy), so a sensitive one only + # gets a non-blocking notice: a maintainer dispatches lane=all on its branch before merging. + if [ "$event" = "pull_request" ] && [ "$1" = "true" ]; then + printf '::notice title=Windows-sensitive::Windows-sensitive (%s): dispatch Cross-platform CI lane=all on this branch before merge\n' "$2" + if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then + printf '**Windows-sensitive** (%s): pull requests do not run the Windows shards; dispatch Cross-platform CI `lane=all` on this branch before merge.\n' "$2" >> "$GITHUB_STEP_SUMMARY" + fi + fi } case "$selected" in @@ -79,6 +87,7 @@ get-ciminstance process.platform localappdata userprofile +spawnsync MARKERS status=0 diff --git a/tests/ci-workflows/ci-review-lanes.test.ts b/tests/ci-workflows/ci-review-lanes.test.ts index 99d0aaf7164..c7dfd629de7 100644 --- a/tests/ci-workflows/ci-review-lanes.test.ts +++ b/tests/ci-workflows/ci-review-lanes.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; import { spawnSync } from "node:child_process"; -import { mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs"; +import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; import { repoPath } from "../helpers/repo-root"; @@ -408,6 +408,8 @@ describe("Windows and nightly CI contracts", () => { test.skipIf(process.platform === "win32" || !Bun.which("git"))("Windows diff scan considers only added src/tests markers and fails closed without a parent", () => { const scenarios = [ { path: "src/example.ts", before: "plain\n", after: "WIN32\n", expected: "true" }, + // An added spawnSync line is Windows-sensitive by content, outside the path filter too (#7468). + { path: "tests/update/example-host-guard.test.ts", before: "plain\n", after: "const original = Bun.spawnSync;\n", expected: "true" }, { path: "tests/example.ts", before: "plain\n", after: "ordinary\n", expected: "false" }, { path: "src/example.ts", before: "powershell\nplain\n", after: "plain\n", expected: "false" }, { path: "docs/example.md", before: "plain\n", after: "pwsh\n", expected: "false" }, @@ -437,13 +439,19 @@ describe("Windows and nightly CI contracts", () => { commit(); if (!scenario.orphan) { writeFileSync(path, scenario.after ?? scenario.before); commit(); } const output = join(directory, "output"); + const summary = join(directory, "summary"); const result = spawnSync("bash", [repoPath("scripts", "ci", "windows-sensitive-diff.sh")], { cwd: directory, encoding: "utf8", timeout: 5_000, - env: { ...process.env, EVENT_NAME: scenario.event ?? "pull_request", PATH_SELECTED: scenario.selected ?? "false", GITHUB_OUTPUT: output }, + env: { ...process.env, EVENT_NAME: scenario.event ?? "pull_request", PATH_SELECTED: scenario.selected ?? "false", GITHUB_OUTPUT: output, GITHUB_STEP_SUMMARY: summary }, }); expect(result.error).toBeUndefined(); expect(result.status, result.stderr).toBe(scenario.status ?? 0); if (scenario.expected) expect(readFileSync(output, "utf8")).toBe(`windows=${scenario.expected}\n`); + // A Windows-sensitive pull request gets a non-blocking notice and summary line, and nothing + // else does; the PR still never runs the Windows shards (AGENTS.md CI policy). + const notice = (scenario.event ?? "pull_request") === "pull_request" && scenario.expected === "true"; + expect(result.stdout.includes("::notice title=Windows-sensitive::")).toBe(notice); + expect(existsSync(summary) && readFileSync(summary, "utf8").includes("lane=all")).toBe(notice); } finally { rmSync(directory, { recursive: true, force: true }); } } }, 30_000);