* fix(#2667): mediate .cmd/.bat/.exe spawns on Windows; split fallow pre-pass failure diagnostic
run-with-timeout spawned .cmd/.bat/.exe commands without shell:true on Windows,
tripping Node's CVE-2024-27980 EINVAL (April 2024 security hardening). The fallow
structural pre-pass then no-op'd silently — a hard execution failure read the same
as 'optional dependency absent'.
(A) gsd-core/bin/gsd-tools.cjs runWithTimeout: gate shell:true on
(win32 && command ends in .cmd/.bat/.exe). Narrow by design — never fires for
the 7 `bash -c` callers (command is `bash`, no such suffix), so the recorded
no-shell-for-argv-array security contract (DEFECT.UNBOUNDED-SUBPROCESS) is
preserved; cmdArgs stays an array. POSIX untouched.
(B) code-review.md fallow pre-pass: name the failure KIND (timeout / spawn failure
/ crash / not-found) so a Windows .cmd spawn failure is not mistaken for an
absent binary.
Regression test in tests/run-with-timeout.test.cjs gated to win32 (.cmd/.bat/.exe
shims run with exit 0 + non-empty stdout; pre-fix EINVAL → exit 125/empty). POSIX
negative-space test guards the unchanged bash -c callers.
* chore(#2667): changeset fragment
* chore(#2667): backfill changeset PR 2897 + correct body (cmd.exe array, not shell:true)
* fix(#2667): exclude .exe from the win32 spawn-mediation gate; ack code-review.md growth
CI caught two failures on the first push:
1. windows-24: 'exits 124 when the wall-clock budget is exceeded' regressed. The
gate matched .exe, so the HANG command (node.exe -e 'setTimeout(...)') was
wrapped in 'cmd.exe /c node.exe ...' — the wrapped child escaped the timeout
cap's process-group reap (exit 124 never fired; hit the 30s harness backstop)
AND cmd.exe risked mis-parsing the -e script arg. .exe is INTENTIONALLY
excluded now: real PE executables spawn fine directly; only .cmd/.bat are the
CVE-2024-27980 EINVAL cases. The .exe test becomes a negative-space test
(node.exe spawned directly, exit 0).
2. ubuntu-22: emitted-attribution — code-review.md grew 1177 bytes from the
#2667 fallow pre-pass failure-KIND case statement; acknowledge it.
---------
Co-authored-by: Test <test@example.com>
(cherry picked from commit 79ed181ec0)
Changeset Fragments
This directory holds per-PR CHANGELOG fragments. Every PR with user-facing changes drops one (or more) <random-name>.md files here describing its CHANGELOG entry. Fragments are consolidated into the top-level CHANGELOG.md at release time.
Why
Two PRs that both edit the ### Fixed block of CHANGELOG.md always conflict on merge — git can't pick a serialization order without human input. Two PRs that each add a fresh .changeset/<unique-name>.md never conflict because they don't share lines.
See #2975 for the full rationale.
Adding a fragment
node scripts/changeset/new.cjs \
--type Fixed \
--pr 1234 \
--body "fix the thing — explain the user-visible change in one sentence"
This writes .changeset/<adjective>-<noun>-<noun>.md with frontmatter and a body. Three random words → concurrent PRs don't collide.
Format
---
type: Fixed
pr: 1234
---
**`/gsd-foo` no longer drops trailing slashes** — explain the user-visible change.
Allowed type: values follow Keep a Changelog: Added, Changed, Deprecated, Removed, Fixed, Security.
Opting out
PRs that legitimately have no user-facing impact can add the no-changelog label. CI honors it. When unsure, add the fragment.
At release time
Promotion is automatic. The release workflow's finalize job runs:
node scripts/changeset/cli.cjs render --version vX.Y.Z --date YYYY-MM-DD --allow-empty
This reads every fragment, groups bullets by type:, replaces ## [Unreleased] with a new ## [vX.Y.Z] - YYYY-MM-DD block, opens a fresh ## [Unreleased] above, and deletes consumed fragments. The --allow-empty flag ensures a no-change release still gets a dated heading (with a _No notable changes._ placeholder). A subsequent verify step confirms the promotion landed correctly. Maintainers do not run this by hand.
Archived fragments
.changeset/archived/ holds fragments for already-shipped releases (≤ 1.3.1), retained for provenance. Their content was hand-curated into the dated ## [1.x.y] sections of CHANGELOG.md during the #690 backfill — they were never consumed by render. All changeset tooling enumerates .changeset/ non-recursively, so archived fragments are never picked up or rendered. Do not move them back to the top level.