ci: fail when unreleased changesets accumulate#1481
Merged
bradygaster merged 1 commit intoJul 19, 2026
Merged
Conversation
The .changeset flow only works if a release consumes the fragments at some cadence — bradygaster#1273 documented 104 pending fragments with the root CHANGELOG a full minor behind, and nothing in CI noticing. This adds a smoke alarm: scripts/check-changeset-drift.mjs counts pending fragments and the age of the oldest one (via the commit that added it), and trips when count > 25 or age > 30 days. Warn-only on PRs since the PR author isn't the one who can fix release cadence; hard fail on dev pushes. SQUAD_CHANGESET_DRIFT_CHECK repo var is the off switch, matching the other gates in squad-ci.yml. Thresholds are deliberately loose — the current tree (64 fragments, oldest 36 days) already trips both, which is the alarm working, not a false positive. Consolidating the backlog itself is a maintainer call tracked on the issue. Part of bradygaster#1273 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
🟡 Impact Analysis — PR #1481Risk tier: 🟡 MEDIUM 📊 Summary
🎯 Risk Factors
📦 Modules Affectedci-workflows (1 file)
root (1 file)
scripts (1 file)
tests (1 file)
This report is generated automatically for every PR. See #733 for details. |
Contributor
🛫 PR Readiness Check
PR Scope: 🔧 Infrastructure
|
| Status | Check | Details |
|---|---|---|
| ✅ | Single commit | 1 commit — clean history |
| ✅ | Not in draft | Ready for review |
| ✅ | Branch up to date | Up to date with dev |
| ❌ | Copilot review | No Copilot review yet — it may still be processing |
| ✅ | Changeset present | No source files changed — changeset not required |
| ✅ | Scope clean | No .squad/ or docs/proposals/ files |
| ✅ | No merge conflicts | No merge conflicts |
| ✅ | Copilot threads resolved | No Copilot review threads |
| ❌ | CI passing | 8 check(s) still running |
Files Changed (4 files, +214 −0)
| File | +/− |
|---|---|
.github/workflows/squad-ci.yml |
+19 −0 |
CONTRIBUTING.md |
+2 −0 |
scripts/check-changeset-drift.mjs |
+113 −0 |
test/scripts/check-changeset-drift.test.ts |
+80 −0 |
Total: +214 −0
This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.
Contributor
🏗️ Architectural Review
Automated architectural review — informational only. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a changeset-drift gate to Squad CI:
scripts/check-changeset-drift.mjscounts pending.changeset/fragments and the age of the oldest one, and trips when count > 25 or age > 30 days. Warn-only on PRs, hard fail on pushes todev.Why
Part of #1273 — the CI-gate half only. The issue documented 104 pending fragments with the root CHANGELOG a full minor version behind, and nothing in CI noticing. Consolidating the backlog itself is a maintainer call (asked on the issue: one-off
changeset versionPR vs inside the release workflow) — this gate doesn't wait on that answer.Threshold rationale in one sentence: loose enough that normal development between releases never trips it (25 fragments / 30 days is several release cycles of headroom), tight enough that a stalled release flow gets caught within a month instead of a version later.
How
scripts/check-changeset-drift.mjs— zero-dep ESM, same shape as the otherscripts/*.mjschecks. Fragments =.changeset/*.mdminusREADME.md(config.jsonis JSON, filtered by extension). Age comes fromgit log --diff-filter=Aon each fragment; untracked/unresolvable files count as age 0 so a PR adding its own changeset can't trip the age check. Threshold logic is a pure exported function.changeset-driftjob insquad-ci.yml:--mode=failonpush(dev),--mode=warnonpull_request— the PR author isn't the one who can fix release cadence, so red PRs would just be noise.vars.SQUAD_CHANGESET_DRIFT_CHECK != 'false'is the off switch, matching the escape-hatch convention of the other gates in this file. Actions pinned to full-length SHAs with trailing version comments (same refs already used elsewhere in the file).fetch-depth: 0because fragment age needs history..squad-templates/workflows/) — this is release hygiene for this repo's changeset flow, not something consumer repos need.Heads up: the gate trips today by design — current tree has 64 pending fragments, oldest 36 days. PRs (including this one) only get a warning annotation, but the first
devpush after merge will show a redChangeset Driftcheck until the #1273 consolidation lands or the repo var turns it off. That's the alarm working; flagging it so nobody is surprised.Testing
test/scripts/check-changeset-drift.test.ts(8 tests, same import style astest/scripts/parse-diff.test.ts): threshold boundaries (at-threshold passes, +1 trips), both-reasons case, custom thresholds, zero fragments, andlistFragmentsfiltering (README.md and config.json excluded, missing dir → empty).--mode=failexits 1.squad-ci.ymlstill parses (js-yaml),npm run lint(tsc) passes after build,npx eslinton the new files — 0 errors.git diff --checkflags CR on them — expected for that file,git diff -wvsgit diffshows no whitespace-only hunks..github/workflows/,scripts/,test/, and CONTRIBUTING.md are not governed source/template paths per the changelog gate's own path regexPR Readiness Checklist
Branch & Commit
devdevBuild & Test
npm run buildpassesnpx vitest run test/scripts/check-changeset-drift.test.ts test/ci/changelog-gate.test.ts— 26 passednpm run lintpasses (type check clean)npm run lint:eslint— 0 errors on changed filesChangeset
Docs
Exports
Breaking Changes
None at merge time. The first dev push after merge will fail the new check until the fragment backlog is consolidated — see the heads-up above.
Waivers
None.