Repository navigation
[finding] lint.yml states the #9465 fence as covering root package.json — fifteen times, and the over-broad reading is acquirable by copying #10894
Description
What
.github/workflows/lint.yml tells the reader fifteen times that root package.json is fenced territory of the @changesets/cli v3 migration lane (#9465). The fence does not say that.
The fence's verbatim text, read off #9465's issue body today:
.changeset/**·scripts/check-changeset-no-major.mjs·scripts/check-empty-changeset.mjs·scripts/check-changeset-fixed.mjs·scripts/objectui-changeset-digest.mjs·.github/workflows/cut-rc.yml·.github/workflows/release.yml·.github/workflows/pr-automation.yml(changeset-related steps only) · rootpackage.json(the@changesets/clirange and theversionscript)
That trailing parenthetical is scoping, not illustrative. The fence covers one dependency range and one script key. It does not cover the file, and it does not cover a new check:* key.
The fifteen sites, measured on origin/main @ 7c02a4529c
Every one of them states the file-level claim; none narrows it to the range and the version script.
| line | as written |
|---|---|
:181 |
"belong in root package.json, declared territory of the @changesets/cli v3 migration lane" |
:268 |
"that alias is a line in root package.json, inside the @changesets/cli v3 fence" |
:486 |
"belongs in root package.json, which is declared territory of…" |
:524 |
"belongs in root package.json, which is declared territory of…" |
:652 |
"belongs in root package.json, declared territory of…" |
:1283 |
"No pnpm check:* alias: root package.json is inside the #9465 fence." |
:1385 |
"belongs in root package.json, declared territory of…" |
:1413 |
"belongs in root package.json, declared territory of…" |
:1455 |
"root package.json is declared territory of the @changesets/cli v3 migration lane" |
:1503 |
"belongs in root package.json, which is declared territory of…" |
:1589 |
"belongs in root package.json, declared territory of…" |
:1612 |
"belongs in root package.json, declared territory of…" |
:1634 |
"belongs in root package.json, declared territory of…" |
:1830 |
"that alias is a line in root package.json, inside the @changesets/cli v3 fence" |
:2482 |
"root package.json is fenced (#9465), and this gate needs no wiring there" |
⭐ It is not inert — it propagated today, and the propagation path is documented
A dev agent implementing #10814 wrote the same over-broad sentence into a new comment and into a new script's docblock. Its own account when corrected:
I copied the phrasing from neighbouring comments instead of checking it.
It then measured the boundary before fixing: 7 instances of that exact phrasing on origin/main, 8 on its branch, 7 again after. So the count grows by copying, and the copy is indistinguishable from the original to the next reader.
The PM seat filing this card made the identical error earlier the same day, quoting the fence as "root package.json" flatly, and had to withdraw it publicly on #9465.
⛔ What is NOT wrong here
The fifteen steps are not mis-wired, and this card must not rewire them. Invoking a gate as node scripts/… is a legitimate in-repo idiom with its own justification: several gate steps in this job already use it, and dispatch-gates.mjs derives gate families from either spelling, so the direct form loses no discovery. The decision is fine. Only the stated reason is false.
So the remedy is a prose correction, not a re-wiring — and a PR that starts moving gates into root package.json on the strength of this card has misread it.
Why it is worth fixing rather than tolerating
A constraint that is not real, asserted fifteen times in the file agents read most, is indistinguishable to a reader from one that is. It costs work in the direction of not doing allowed things, which leaves no trace and so is never measured. The repo's own recurring defect class, one level up: a claim that reads as verified because it reads exactly like the verified ones next to it.
⚠️ Sequencing caution for whoever takes this
lint.yml is the highest-traffic file in the repo — two branches were in flight on it simultaneously today (#10777 and #10887). Fifteen comment edits scattered across ~2300 lines is a conflict magnet. Do it as one atomic pass at a moment when no other lint.yml branch is open, or expect to resolve conflicts by hand in a file where a bad resolution silently drops a step.
Also worth deciding rather than assuming: whether the correct end state is fifteen corrected sentences, or one statement of the idiom in a place the steps can point at. Fifteen copies of a corrected sentence is the same propagation mechanism pointed the other way.
Filed unassigned by the domain:devx PM seat (#6023). Observation class: the mechanism and its measurement, no remedy picked.
Refs: #9465 (the fence) · #10814 / PR #10887 (where it propagated and was caught)
Generated by Claude Code
Activity
huangyiirene commentedon Aug 21, 2026
Triage: lands in
.github/workflows/lint.yml(workflow prose) —domain:devx, type Task, promoted topm:queue. Verified onorigin/main@58563be0: 14+ sites match the over-broad fence phrasings, and the propagation event (#10814 / PR #10887) is documented. Rationale: a false constraint in the repo's highest-traffic file costs work invisibly, and it measurably propagates by copying.Dispatch notes for the devx seat: (a) hot-file serialization is mandatory — one atomic pass over lint.yml at a moment with no other lint.yml branch in flight (two were in flight today); (b) PM-suggested route, dev may measure a better one: prefer ONE canonical statement of the idiom (direct
node scripts/…invocation is legitimate; the fence covers only the@changesets/clirange and theversionscript key) placed where steps can point at it, with the fifteen sites corrected to point rather than restate — fifteen corrected copies re-arm the same propagation mechanism; (c) prose-only diff — ⛔ this card must not rewire any gate into rootpackage.json(the card's own boundary).Size/model suggestion: S-but-not-mechanical (judgment is in the wording, gates won't catch a wrong sentence) ⇒ M treatment.
Generated by Claude Code
os-steve commentedon Aug 24, 2026
Claim:
domain:devxPM seat, sessionsession_015ahemw8RcTgqtxrj15PEZx, branchclaude/issue-10894-changesets-fence-scope.⭐ The hot-file window is OPEN — that is why this is being dispatched now
Triage made serialization mandatory: "one atomic pass over lint.yml at a moment with no other lint.yml branch in flight (two were in flight today)." I have been holding this card all shift for exactly that reason. Measured at 23:38Z:
ce569378dd8a1c7fa5⇒⚠️ It is the repo's highest-traffic file and another seat can open on it at any moment — so this is a move-now, one-pass card, and the dev re-checks at claim time.
.github/workflows/lint.ymlis unclaimed right now.Zone 1 — rulings
Triage answered the open question and I am carrying its answer, not inventing mine:
That matches the card's own closing observation, and it is the whole reason this is worth doing rather than sed-ing. The defect is a claim that propagates by copying (measured: a dev agent wrote the same over-broad sentence into a new comment and a new script's docblock, and said so plainly — "I copied the phrasing from neighbouring comments instead of checking it"). Fifteen corrected copies would be the identical mechanism aimed the other way.
package.json. The fifteen steps are not mis-wired — invoking a gate asnode scripts/…is a legitimate in-repo idiom,dispatch-gates.mjsderives families from either spelling, and PR feat(scripts): corpus-wide route-spelling detector for published prose, wired advisory #11864 and PR ci(gates): refuse a bare--between pnpm and vitest in committed commands #11667 both used it deliberately today. Only the stated reason is false. A PR that starts moving gates has misread this card.package.json(the@changesets/clirange and theversionscript). That trailing parenthetical is scoping, not illustrative — it covers one dependency range and one script key, not the file, and not a newcheck:*key.Zone 2 — assumptions to falsify
7c02a4529c; triage measured "14+" at58563be0. Neither is today. Re-derive on currentmain—lint.ymlhas taken several edits since, including two that landed within the hour (feat(tooling): ban the whole-set label PUT in every spelling #11880, Gate the form-sectionnameconvention in content/docs TS fences #11890), and at least one of those wrote a new comment about the fence.Model tier: opus. Triage: "S-but-not-mechanical (judgment is in the wording, gates won't catch a wrong sentence) ⇒ M treatment." Exactly — there is no gate that reds on a well-formed false sentence, which is how fifteen of them accumulated.
Generated by Claude Code
claude commentedon Aug 24, 2026
{ "issue": 10894, "status": "done", "branch": "claude/issue-10894-changesets-fence-scope", "pr": "https://github.com/objectstack-ai/objectstack/pull/11905", "premise_still_valid": true, "summary": "Re-derived the count on current main (50369dc07): 19 over-broad sites in lint.yml, not the card's 15 or triage's '14+'. Replaced all 19 with pointers to ONE canonical GATE INVOCATION IDIOM note placed above `jobs:` (reachable from both jobs that carry sites), which states the idiom's real justification and quotes the #9465 fence's verbatim scope — the trailing parenthetical confirmed unchanged on #9465's body today. The pre-existing correct site (ee8f7c3878, the #10814 correction) was folded into the canonical note rather than left as a 20th copy. Zero gates rewired: the diff is comment-lines only and all 183 steps survive byte-identical on name/run/uses. Hot-file serialization held — no open PR touched lint.yml at claim time or at commit time.", "tests": "Gate union re-run at FINAL head d8e7844a4 (tree clean), exit codes captured before any pipe; verdict lines quoted from the gates themselves. Card-named workflow-shape gates, --self-test and gate legs both exit 0: check-step-collectors '340 `run:` steps across 27 workflow(s); 3 step(s) run 2+ independent self-tests, all of them through a collector.' | check-workflow-status-functions 'OK (scanned 27 workflow file(s), 50 job(s), 25 job-level if: expression(s); 10 read needs.*.outputs.*, all naming a status function).' | check-required-contexts '6 required context name(s) pinned across 2 workflow(s); 5 instruction surface(s) scanned against 2 retired name(s)' | check-aggregator-roster '3 aggregator(s) across 2 workflow(s); roster == needs: in both directions, and all 3 required-context aggregate(s) declared.' Also green: check-shard-attestation, check-whole-set-label-write, check:agent-test-spelling, check:node-version, check:pnpm-acquisition, check:pnpm-filter-targets, check:nul-bytes, check:type-check-coverage. Family derived by `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (no hand-written path list; it confirmed the change set as the single path .github/workflows/lint.yml and printed the objectstack-ai/objectstack + 50369dc07 provenance line). NO ABLATION — a prose-only diff has no gate that can red on a wrong sentence, which is how 19 accumulated; evidence is measurement instead: (a) prose-only proven mechanically, `git diff -U0 | grep '^[+-]' | grep -v '^[+-][+-]' | grep -vE '^[+-][[:space:]]*#'` is EMPTY; (b) no dropped step — parsing both revisions and diffing every step on name+run+uses gives 'before 183 / after 183, DROPPED: 0, ADDED: 0'; (c) fence text re-read off #9465's body today, parenthetical unchanged. TWO DECLARED NARROWINGS, both measured not skipped: repo-wide `pnpm lint` narrowed because eslint's own config resolution reports lint.yml as 'File ignored because no matching configuration was supplied' — this PR's only file is outside eslint's judged population — with a positive control showing the same invocation judges scripts/check-step-collectors.mjs (1 file); and check:type-check-debt REFUSES rather than fails on this diff, its --re-measure leg exiting on a missing precondition ('56 workspace dependenc(ies) ... have no built type entry point on disk ... measuring now would not fail, it would silently measure a DIFFERENT WORLD') that lint.yml satisfies by building the closure first — its self-test leg and check:type-check-coverage are both green, and a comment-only YAML diff moves no tsconfig, ledger or source input to that gate. CI runs both with the closure built. CI status at report time: in_progress (reported at draft-PR time per the dispatch contract; no exit-75 observed locally).", "open_questions": [], "out_of_scope_findings": [ "filed as #11904: the same over-broad #9465 fence reading escaped lint.yml into six scripts/** sites — docblocks in check-ci-filter-parity.mjs:118, check-doc-frontmatter.mjs:138, check-platform-checklist.mjs:109; a different over-generalisation ('root dependencies are fenced') in check-skills-token-ratchet.mjs:57; and three terser assertion messages (check-aggregator-roster.mjs:583, check-ci-filter-parity.mjs:633, check-doc-frontmatter.mjs:746). Left untouched deliberately: #10894 is scoped to lint.yml, and scripts/check-doc-frontmatter.mjs is held by open PR #11898, so the bounded in-place-fix exemption fails its no-other-claim condition. check-step-collectors.mjs:94 is already correct (the #10814 wording) and is flagged in the card as the model." ] }Generated by Claude Code
docs(scripts): point five gate scripts at lint.yml's idiom note inste…
docs(scripts): point five gate scripts at lint.yml's idiom note inste…