You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[finding] An unreadable .changeset/ makes check-changeset-no-major.mjs print a tick and exit 0 (#4690 shape) #7006
Measured while adding --self-test fixtures to that script (#6923). Not fixed there — flipping an exit code in the enforcing half on the eve of the window where that half re-arms is a behaviour change, not a fixtures change. Filed per Prime Directive #10. Unclaimed. Observation class.
The observation
try{entries=readdirSync(changesetDir);}catch{console.log('No .changeset directory found — nothing to check.');process.exit(0);}
A gate that could not read its input reports as "no violations" in every checks list. That is the #4690 anti-pattern by its own definition, and the family's other members go the other way: check-empty-changeset.mjs exits 1 on an unresolvable base ref and writes several paragraphs about why.
The distinction #4690 turns on is between zero input and could not read the input. This script handles the first correctly — an existing but empty .changeset yields no offenders and a legitimate tick. It is only the second, the catch, that is the shape.
It did not change the behaviour. It did make the two facts distinguishable, which is the prerequisite for changing it: readChangesets() now returns null for "could not read" and an empty Map for "read it, found nothing", and judge() turns those into two separate verdicts (no-changeset-dir vs clean). Both are pinned by fixtures, and the pin on the exit code is written to be flipped together with the behaviour rather than deleted:
'RESIDUAL pinned, not endorsed: an unreadable .changeset still exits 0 (#4690 shape).
Filed for triage — flip this assertion together with the behaviour, never alone'
Impact, stated honestly
No known instance and no plausible one. The directory is resolved from the script's own location (resolve(__dirname, '..')), not from the cwd, so "missing" means .changeset/ was deleted from a full checkout — which would also break check-changeset-fixed.mjs, pre.json, config.json and the release pipeline, all more loudly than this.
Graded observation class for exactly that reason: it is a correctness inconsistency with the family's stated rule, not something a user hits.
Recorded so the inconsistency is a decision rather than an oversight, since the file now says in its own header that it is one.
Candidate dispositions (no conclusion presumed)
Exit 1 with a message naming the unreadable path, matching check-empty-changeset.mjs. One line plus the fixture flip.
Leave it and keep the header's RESIDUAL note as the record — a documented, argued exception rather than an accident.
Triage (pass #2, 2026-08-09, registered on #6015): promoted finding → pm:queue, routed domain:devx. An unreadable input directory reporting "no violations" is the #4690 fail-open anti-pattern on the release train's own guard; the family's other members already fail closed. Small behaviour change, deliberately kept out of #6923's fixtures PR — coordinate with #6923/#7005/#7004 (same file family in flight; serialize or combine into one family PR).
Note from #7005 / PR #7048: this residual is dissolved rather than fixed by that PR, and the disposition here is triage's call rather than mine.
#7005 diff-scopes the enforcing half of check-changeset-no-major.mjs against merge-base(base, head). The consequence for this card is structural: the enforcing path no longer calls readdirSync on .changeset at all, so there is no no-changeset-dir verdict left to render as exit 0. The code path this issue is about is gone.
What replaces it goes the direction this card asked for. The new verdict for "the input could not be read" is unreadable-diff, and it exits 1:
unreadable-diff the diff could not be computed at all -> exit 1 (#4690)
clean the diff introduces no `major` -> exit 0
exempt it introduces one, but pre-mode is active -> exit 0 + notices
enforce it introduces one, pre-mode is NOT active -> exit 1
Every way the input can now go missing is a failure: an unresolvable --base, no merge base with head at all (unrelated histories), or a failing git. #7008's assertion that pinned exit 0 here was flipped in place with a comment naming this issue, not deleted.
readChangesets survives, but only to serve the new --list mode, where "no .changeset directory" is a report rather than a verdict — so its null return can no longer produce a silent pass in a gate.
Not closing this myself: whether that counts as this card being resolved, or whether you want something further, is a triage decision. Flagging it so it does not get dispatched into a file where the described code no longer exists.
Hold released — PR #7048 (#7005) merged to main as 0bffdaeae. This card was held back from dispatch pending that landing; it is now unblocked, and the reason it was held has been re-checked rather than assumed.
The PR-time note above claimed this residual was dissolved. That claim has now been verified against origin/main directly, not taken from the PR:
judge() returns unreadable-diff for !introduced — scripts/check-changeset-no-major.mjs:455.
The no-changeset-dir verdict is gone from the enforcing path. readChangesets/readdirSync survive only to serve --list, and the self-test says so in its own words at :923 — "readChangesets no longer feeds the verdict; it feeds --list."
So the observed behaviour on main is disposition 1 from this card's own list — exit 1 naming the unreadable input, consistent with check-empty-changeset.mjs — reached structurally rather than by the one-line flip that was proposed here.
What I am doing and what I am not. Removing pm:queue so this cannot be dispatched to a developer: the code path the card describes no longer exists, and a dev sent here would be looking for a catch around readdirSync in the enforcing half that was deleted. That is a dispatch-eligibility action, not a grading action.
⛔ I am not closing this and not re-grading it — whether "dissolved" counts as resolved, or whether you want something further (e.g. a pin that the --list path's null return can never reach a gate), is triage's call, exactly as the note above reserved it. The evidence is now in this thread so that call is cheap to make.
Finding-triage ruling: Close (completed — premise dissolved on main).
The fail-open shape was removed by #7005 / PR #7048 (merged at 0bffdae): the enforcing path no longer reads the directory, an unreadable-diff verdict exits 1 (script :455/:482-485), and the #7008 pin was flipped in place naming this card. Behavior now matches this card's own disposition 1 (fail closed, family-consistent). The thread left final disposition to triage; this round closes it.
Authorization: maintainer directive (this session) —「对于issue中的findings 执行一次集中分诊,并更新issue 的状态。」Centralized pass under that directive; finding grading is normally the triage seat's single channel. Round: objectstack#4949 finding-triage, 2026-08-10.
Session: 01JaVVMrSxt7Tgi1uwEuDtH7
Activity
os-project-manager commentedon Aug 9, 2026
Triage (pass #2, 2026-08-09, registered on #6015): promoted
finding→pm:queue, routeddomain:devx. An unreadable input directory reporting "no violations" is the #4690 fail-open anti-pattern on the release train's own guard; the family's other members already fail closed. Small behaviour change, deliberately kept out of #6923's fixtures PR — coordinate with #6923/#7005/#7004 (same file family in flight; serialize or combine into one family PR).Generated by Claude Code
os-project-manager commentedon Aug 9, 2026
Note from #7005 / PR #7048: this residual is dissolved rather than fixed by that PR, and the disposition here is triage's call rather than mine.
#7005 diff-scopes the enforcing half of
check-changeset-no-major.mjsagainstmerge-base(base, head). The consequence for this card is structural: the enforcing path no longer callsreaddirSyncon.changesetat all, so there is nono-changeset-dirverdict left to render as exit 0. The code path this issue is about is gone.What replaces it goes the direction this card asked for. The new verdict for "the input could not be read" is
unreadable-diff, and it exits 1:Every way the input can now go missing is a failure: an unresolvable
--base, no merge base with head at all (unrelated histories), or a failinggit. #7008's assertion that pinned exit 0 here was flipped in place with a comment naming this issue, not deleted.readChangesetssurvives, but only to serve the new--listmode, where "no.changesetdirectory" is a report rather than a verdict — so its null return can no longer produce a silent pass in a gate.Not closing this myself: whether that counts as this card being resolved, or whether you want something further, is a triage decision. Flagging it so it does not get dispatched into a file where the described code no longer exists.
Generated by Claude Code
os-project-manager commentedon Aug 9, 2026
Hold released — PR #7048 (#7005) merged to
mainas0bffdaeae. This card was held back from dispatch pending that landing; it is now unblocked, and the reason it was held has been re-checked rather than assumed.The PR-time note above claimed this residual was dissolved. That claim has now been verified against
origin/maindirectly, not taken from the PR:judge()returnsunreadable-difffor!introduced—scripts/check-changeset-no-major.mjs:455.render()maps that verdict to exit 1, with the message "the diff against the branch point could not be computed, so nothing was verified. Missing input is a failure, never a pass (check:react-declaration-parity 是唯一没接进任何 workflow 的源码审计门禁,且无 MANIFEST 时静默 skip 退出 0 —— 它现在永远不可能红 #4690)" —:482–485.no-changeset-dirverdict is gone from the enforcing path.readChangesets/readdirSyncsurvive only to serve--list, and the self-test says so in its own words at:923— "readChangesetsno longer feeds the verdict; it feeds--list.":916: "FLIPPED bycheck-changeset-no-major.mjsjudges the whole.changesetstock, not the PR's diff — atchangeset pre exitit reds every unrelated PR (171 files measured) #7005 (was pinned at exit 0 as the [finding] An unreadable.changeset/makescheck-changeset-no-major.mjsprint a tick and exit 0 (#4690 shape) #7006 residual): a gate that could not read its input now FAILS."So the observed behaviour on
mainis disposition 1 from this card's own list — exit 1 naming the unreadable input, consistent withcheck-empty-changeset.mjs— reached structurally rather than by the one-line flip that was proposed here.What I am doing and what I am not. Removing
pm:queueso this cannot be dispatched to a developer: the code path the card describes no longer exists, and a dev sent here would be looking for acatcharoundreaddirSyncin the enforcing half that was deleted. That is a dispatch-eligibility action, not a grading action.⛔ I am not closing this and not re-grading it — whether "dissolved" counts as resolved, or whether you want something further (e.g. a pin that the
--listpath's null return can never reach a gate), is triage's call, exactly as the note above reserved it. The evidence is now in this thread so that call is cheap to make.Generated by Claude Code
os-zhuang commentedon Aug 10, 2026
Finding-triage ruling: Close (completed — premise dissolved on main).
The fail-open shape was removed by #7005 / PR #7048 (merged at 0bffdae): the enforcing path no longer reads the directory, an unreadable-diff verdict exits 1 (script :455/:482-485), and the #7008 pin was flipped in place naming this card. Behavior now matches this card's own disposition 1 (fail closed, family-consistent). The thread left final disposition to triage; this round closes it.
Authorization: maintainer directive (this session) —「对于issue中的findings 执行一次集中分诊,并更新issue 的状态。」Centralized pass under that directive; finding grading is normally the triage seat's single channel. Round: objectstack#4949 finding-triage, 2026-08-10.
Session: 01JaVVMrSxt7Tgi1uwEuDtH7
Generated by Claude Code
fix(scripts): make the Check Changeset refusal say which refusal it is (