fix(3621): release-sdk hotfix cherry-picks test-fixture commits (#3623)

* fix(3621): cherry-pick test-fixture commits in hotfix runs

The release-sdk hotfix loop excluded test-fixture updates that align CI
with a cherry-picked production fix, leaving the hotfix branch with new
production behavior and stale test assertions. Broke v1.42.3 CI (run
25949422676) when fix(3562) was cherry-picked but its bundled test
correction in docs(3562) commit 08848df8 was POLICY_SKIPPED by the
prefix filter.

Two-part fix:

1. release-sdk.yml prefix regex now accepts test: alongside fix:/chore:.
   feat:, docs:, refactor: still POLICY_SKIPPED as before.

2. scripts/diff-touches-shipped-paths.cjs treats tests/-rooted paths and
   sdk/src vitest specs as CI-gating-equivalent. A test: commit touching
   only those paths now passes the shipped-paths gate.

The #2980 push-blocking guard is preserved as a separate first-priority
check: any commit touching .github/workflows/<file> still skips
regardless of test paths in the same bundle, because the default
GITHUB_TOKEN lacks the workflow scope and the push step would fail.

New regression coverage in tests/bug-3621-cherry-pick-test-fixtures.test.cjs:
- workflow prefix regex includes test:
- isCiGating accepts tests/ and sdk/src vitest specs, rejects
  non-spec sdk/src paths and incidental "test" name occurrences
- classifier exits 0 for test-only, mixed test+docs, and the original
  shipped paths
- classifier exits 1 for pure docs-only and workflow-only diffs
- new explicit assertion that #2980 push-blocking wins over #3621:
  workflow + test + changelog bundle still skips

Adjusted one pre-existing bug-2980 test fixture to use a non-
push-blocking non-shipped path (planning/notes.md) instead of
.github/workflows/release-sdk.yml. The original assertion was
documenting "mixed diff includes shipped path → include" but its
fixture happened to also trigger the push-blocking guard now made
explicit by this PR.

Fixes #3621

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(release-sdk): align hotfix summary labels with test matcher

* fix(3621): align operator-facing strings with the fix/chore/test matcher

The candidate-loop regex was updated to accept test: but several
human-facing strings in the same job still read fix/chore. Update every
description/comment/summary line for consistency so operators reading
the run summary or workflow_dispatch inputs see the same set of accepted
prefixes the matcher actually applies.

Also corrected the NON_SHIPPED_SKIPPED summary text — it claimed test
changes belong on main, not in a hotfix. That assumption is what #3621
fixes; tests under tests/ and sdk/src vitest specs are now CI-gating
candidates and may be picked. The summary now scopes the never-pick
guidance to CI / docs / planning paths only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-05-15 22:43:21 -04:00
committed by GitHub
parent 79ea076daa
commit 0efe4f0612
5 changed files with 288 additions and 17 deletions

View File

@@ -12,6 +12,12 @@
* - package.json (always included by `npm pack`, regardless of `files`)
* - every entry in package.json `files`, treated as either an exact
* file match or a directory prefix (matching `npm pack` semantics).
* - CI-gating test paths: `tests/<anything>` plus
* `sdk/src/<anything>/<name>.test.<ts|cjs|mjs|js>` and `.spec.` variants
* — these don't ship in the tarball, but they gate the hotfix-branch
* test job. A test fixture update that aligns with a cherry-picked
* production fix MUST be pickable or CI fails on the hotfix run.
* #3621 — root cause of the v1.42.3 hotfix red CI.
*
* `package-lock.json` is intentionally NOT considered shipped — `npm pack`
* excludes it from the tarball unless it's explicitly in `files`, and at
@@ -48,6 +54,24 @@ function loadShipPrefixes(pkgPath) {
return ['package.json', ...files];
}
// #3621: paths that gate hotfix-branch CI even though they don't appear
// in the npm tarball. When a cherry-picked production fix changes behavior
// that an existing test on the v1.42.2 base asserts against, the matching
// test fixture from `main` must also be cherry-picked or CI fails on the
// hotfix run (exactly what happened on v1.42.3 — production fix(3562) was
// picked, the bundled test-fixture correction in commit 08848df8 was not).
// Combined with the `test:` prefix being added to the candidate-loop regex
// in release-sdk.yml, this lets `test(####):` fixture-alignment commits be
// cherry-picked alongside their production counterparts.
function isCiGating(diffPath) {
if (diffPath.startsWith('tests/')) return true;
// SDK vitest specs live next to source. Production source ships via
// sdk/dist/ (already in package.json `files`); the test files are what's
// missing from that surface.
if (diffPath.startsWith('sdk/src/') && /\.(test|spec)\.(ts|cjs|mjs|js)$/.test(diffPath)) return true;
return false;
}
function isShipped(diffPath, shipPrefixes) {
// Normalize Windows-style separators just in case (git always emits
// forward slashes, but a developer running this locally on a different
@@ -56,6 +80,14 @@ function isShipped(diffPath, shipPrefixes) {
return shipPrefixes.some((s) => p === s || p.startsWith(s + '/'));
}
// #2980: commits that touch `.github/workflows/*` cannot be cherry-picked
// onto a hotfix branch because the default GITHUB_TOKEN lacks the
// `workflow` permission and the push step fails. Detect them upfront so a
// `test:`-eligible commit bundling a workflow edit still gets skipped.
function isPushBlocking(diffPath) {
return diffPath.replace(/\\/g, '/').startsWith('.github/workflows/');
}
function fail(message, err) {
process.stderr.write(`diff-touches-shipped-paths: ${message}\n`);
if (err && err.stack) process.stderr.write(`${err.stack}\n`);
@@ -86,8 +118,22 @@ function main() {
process.stdin.on('end', () => {
try {
const paths = buf.split('\n').map((s) => s.trim()).filter(Boolean);
const hit = paths.some((p) => isShipped(p, shipPrefixes));
process.exit(hit ? EXIT_SHIPPED : EXIT_NOT_SHIPPED);
// #2980 still wins over #3621: any commit touching .github/workflows/*
// is unpickable regardless of other content because the push step
// fails on workflow scope rejection. Check this first.
if (paths.some(isPushBlocking)) {
process.exit(EXIT_NOT_SHIPPED);
}
if (paths.some((p) => isShipped(p, shipPrefixes))) {
process.exit(EXIT_SHIPPED);
}
// #3621: a commit whose only relevant paths are CI-gating tests is
// still pickable — it can change whether the hotfix CI passes even
// though it doesn't change what the npm tarball ships.
if (paths.some(isCiGating)) {
process.exit(EXIT_SHIPPED);
}
process.exit(EXIT_NOT_SHIPPED);
} catch (err) {
fail('classification failed', err);
}
@@ -98,4 +144,4 @@ if (require.main === module) {
main();
}
module.exports = { loadShipPrefixes, isShipped, EXIT_SHIPPED, EXIT_NOT_SHIPPED, EXIT_ERROR };
module.exports = { loadShipPrefixes, isShipped, isCiGating, isPushBlocking, EXIT_SHIPPED, EXIT_NOT_SHIPPED, EXIT_ERROR };