Skip to content

test(ci): pin the skip-changeset phantom — nothing wires it, and the page keeps denying it - #6274

Merged
yinlianghui-tw merged 1 commit into
mainfrom
claude/issue-4912-pin-skip-changeset-phantom
Aug 25, 2026
Merged

test(ci): pin the skip-changeset phantom — nothing wires it, and the page keeps denying it#6274
yinlianghui-tw merged 1 commit into
mainfrom
claude/issue-4912-pin-skip-changeset-phantom

Conversation

@yinlianghui-tw

Copy link
Copy Markdown
Collaborator

Part of #4912

⚠️Part of, not Fixes, deliberately.#4912 closes when both halves are done: this
pin, and the deletion of the skip-changesetlabel object. I could not perform the
deletion (evidence below), so a closing keyword here would silently close a card whose
administrative half is still owed.

What this changes

Two files, no runtime code:

filechange
scripts/__tests__/ci-cd-pipeline-doc.test.ts+2 cases in the existing phantom-mechanism family
content/docs/guide/ci-cd-pipeline.mdcorrects a claim on the page that had become false

The card got stronger while it sat — the predicted harm actually happened

#4912 predicted: "the next agent that greps for 'how do I declare this PR publishes nothing'
finds a label, applies it, and believes the declaration landed."

That occurred on 2026-08-25, hours before this PR. Verified rather than taken on trust:

So the label is no longer a latent phantom — it is actively being read as a mechanism by
agents, which is the harm the card described.

Premise re-measured on origin/main @ ef2a3bd8d — unchanged

$ git grep -n 'skip-changeset' origin/main -- .github scripts package.json
scripts/__tests__/ci-cd-pipeline-doc.test.ts:184: * changeset gate skippable with a `skip-changeset` label; neither the workflow nor the

Exactly one hit, the prose the card names. No changeset gate has a label-keyed skip path:
check-changeset-presence.mjs, check-changeset-fixed.mjs and check-changeset-no-major.mjs
contain no read of any label. No workflow reads it either.

⚠️One measurement came out bigger than the card knew. The card names PR #4639 as carrying
the label. It is on seven PRs, all closed: #4700, #4665, #4639, #4630, #4628, #4615, #4129.
⛔ I removed it from none of them — I opened none of them.

What the pin can and cannot observe

A label lives in GitHub's data, not in the tree. No test here can assert the label object is
gone
, and this suite has no network call or credential anywhere in it — every other assertion
reads the filesystem. Reaching for the labels API to pin this would be the one test in the file
that can fail because of an outage.

⚠️ It was also correctly objected on the card that a pin merely restating "nothing reads the
label"
is already true, stays true, and does not address the defect. That objection is why the
pin is not that. The two cases hold things a future commit could change without anyone
noticing:

  1. never wires the phantom skip-changeset label into a workflow or a gate — walks
    .github/ and scripts/ and fails on any occurrence outside the test file that records the
    history. This is the guard against option B landing without a decision: a labelled bypass
    on a changeset gate that deliberately has none, declined by .github/WORKFLOWS.md documents 5 workflows that do not exist and omits 9 that do — including a changeset gate and a skip-changeset label neither of which is real #3724 and again by the A skip-changeset label exists on this repo, but no workflow reads it — the repo's own test records that neither the workflow nor the label was ever real #4912
    ruling. The name is wired in the objectstack sibling (lint.yml, pr-automation.yml,
    check-empty-changeset.mjs), which is how it reaches agents who then look for it here, so
    copying that wiring across is a live risk, not a theoretical one.
  2. keeps the page denying skip-changeset rather than describing itci-cd-pipeline.md
    is where a contributor looks up "how do I declare this PR publishes nothing", so the page is
    what decides whether the next reader believes the label. Unlike the size-check.yml pin
    above it, absence is the wrong assertion: the page must keep naming the label in order to
    deny it. So the denial itself is pinned, along with the alternative to reach for instead — a
    denial with no alternative sends the reader back to the label.

⭐ The page was asserting something false about this very phantom

Found while verifying, and it is the reason this PR is worth landing before the deletion
rather than after. The page carried:

The label still does not exist (checked against the labels API, 2026-08-08 — of the two names
only dependencies exists, applied by the auto-labeler and read by no gate)

That is false today. Measured via get_label on objectstack-ai/objectui:

name: skip-changeset
color: ededed (GitHub's default grey)
description: "" (empty)

The object exists, with exactly the auto-minted signature the card describes. So the worst
possible combination was in place: a contributor reads "the label does not exist", sees it in
the label picker, concludes the page is stale — and treats the label as the real mechanism.

A point-in-time API reading is not a fact this repository can keep true, and this one was
false within weeks. It is replaced with the durable invariant (nothing reads it; here is what to
do instead), and the third assertion pins that a point-in-time reading is not put back.

Verification

All on the final commit 1a1e7bc07 (git rev-parse --short HEAD), clean tree. Exit codes
captured by redirect before any pipe (cmd > out 2>&1; echo EXIT=$?); every line quoted is
the gate's own printed verdict, never a bare $?.

gateexitits own verdict line
vitest run scripts/__tests__/ci-cd-pipeline-doc.test.ts --reporter=verbose0Test Files 1 passed (1) · Tests 34 passed (34)
type-check:scripts0pnpm echoed > tsc -p tsconfig.scripts.json
lint:rootUNNARROWED0✖ 28 problems (0 errors, 28 warnings)
check:control-bytes0✅ check-control-bytes: OK (scanned 5171 tracked text file(s); skipped 85 binary).
check-doc-links.mjs0Links are valid across 15 scan roots.
check-changeset-presence.mjs0✅ No source of a released package changed in this range, so no changeset is owed.
vitest run scripts/ (the narrowed union)0Test Files 77 passed (77) · Tests 2209 passed (2209)

Both new cases confirmed COLLECTED BY NAME under --reporter=verbose, not inferred from a
total:

✓ |unit| scripts/__tests__/ci-cd-pipeline-doc.test.ts > ci-cd-pipeline.md — workflow inventory
> never wires the phantom `skip-changeset` label into a workflow or a gate 25ms
✓ |unit| scripts/__tests__/ci-cd-pipeline-doc.test.ts > ci-cd-pipeline.md — workflow inventory
> keeps the page denying `skip-changeset` rather than describing it 4ms

lint:root was run unnarrowed. Its 28 warnings are all pre-existing
@typescript-eslint/no-explicit-any in e2e/live/**, vitest.setup.base.ts and two
scripts/__tests__/vite-* files — zero in either file this PR touches (grep -c 'ci-cd-pipeline' over the lint output → 0), and the same set PR #6260 reported.

Reverse-verification — both pins fail when violated

Run from the committed state, each leg with a trap … EXIT INT TERM restore so a cap-kill
could not leave the tree mutated. No build or dist/ is involved — vitest transforms these
tests from source, and the ablation exercised the byte-identical walker and normalizer.

legmutation confirmed on diskresult
plant .github/planted-ablation-probe.yml reading the labelgrep -c in planted file → 1, file exists → 1offenders → [".github/planted-ablation-probe.yml"] (detected)
remove the plantfile absent → 1offenders → [] (restored)
strip the denial sentence from the pageanchor hits → 1; denial count after edit → 0; git diff --stat non-emptyassertion subject gone (detected)
git checkout -- the pagedenial count → 1restored, git status --porcelain empty

⚠️ Mutation was confirmed by counting the anchored text, never by an editor's exit status —
a zero-match replace exits 0 and would have produced a green no-op ablation.

⚠️ ONE DECLARED NARROWING

The full root vitest suite was not run here; it is narrowed to vitest run scripts/. Reason:
this container caps a foreground command at ~10 minutes, and the shared verify lock was held by
sibling agents running the full farm — one acquisition attempt burned its whole budget and
returned VERDICT queue-timeout (exit 99) · never acquired. That run is CI's.

The narrowing is measured, not assumed:

  • Population — 2 changed files. One is the test file itself, run explicitly and verbosely.
    The other is markdown; no package imports it.
  • Readers of the changed docgit grep -ln 'ci-cd-pipeline' over source and tests returns
    10 files. Exactly one reads the file: scripts/__tests__/ci-cd-pipeline-doc.test.ts
    (docPathreadFileSync). Of the rest,
    scripts/__tests__/check-action-forward-parity.test.ts uses the path only as a string
    literal in a glob-match example
    , packages/auth/src/__tests__/reserved-auth-features.test.ts
    names it only in a prose comment ("follows the … pattern"), and the others match on the
    unrelated dependabot-merge-gate / lint-workflow prose.
  • Config invariance — the new assertions add no new verification surface: they read the same
    two files the suite already read, with node:fs the file already imports.

Changeset — none, deliberately

check-changeset-presence.mjsexits 0 whether or not a changeset is present, so it is not
deciding this. I read the diff: a test file and a documentation page, neither of them source of
any published package, with no behaviour a release note could describe. The gate's own count
agrees — 2 file(s) changed, 0 of them published source of a package the release covers.
Precedents that carried none: #6216, #6212, #6260.

And no skip-changeset label on this PR — on this card of all cards, applying an inert
label to declare "publishes nothing" would re-create the exact defect the PR exists to pin.

⛔ The label deletion — attempted, and I could not do it

Reported plainly rather than worked around.

  • The GitHub MCP toolset exposes get_label only — read. There is no create, update, or
    delete label tool; confirmed by searching the tool surface, not assumed.
  • Direct REST from this seat is refused:
$ curl -sS -w 'HTTP %{http_code}' -H "Authorization: Bearer $GITHUB_TOKEN" \
https://api.github.com/repos/objectstack-ai/objectui/labels/skip-changeset
HTTP 403
{"message":"GitHub access is not enabled for this session. An org admin must connect
the Claude GitHub App for this organization."}

(GITHUB_TOKEN in this container is the literal 14-byte string proxy-injected; the real
credential is added by the proxy, and the proxy refuses this route.)

The remaining act is DELETE /repos/objectstack-ai/objectui/labels/skip-changeset, or
Settings → Labels.
Nothing is lost by it: nothing reads the label, and all seven PRs carrying
it are closed. #4912 stays open until it is done.

Not armed

⛔ Draft, and auto-merge is deliberately NOT enabled — that is the PM's call after review
against GitHub.


Generated by Claude Code

…e page keeps denying it
The label object was re-minted in this repository (auto-created by being applied
by name) and is now being read as a mechanism: a triage comment on #6243
instructed a PR to carry it, the developer refused, and the refusal was upheld
on PR #6260.
A test cannot see GitHub's label data, so it does not try. It pins the two
halves that live in the tree: no read of the name under .github/ or scripts/
(option B landing without a decision), and the page keeping its denial plus the
alternative to reach for instead.
Also corrects a claim on that page that had become false: it reported a
point-in-time labels-API reading from 2026-08-08 saying the label did not
exist. It does. A reading nothing in the tree can keep true is replaced by the
durable invariant, and the third assertion pins that it is not restored.
@yinlianghui-twClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM review — ACCEPT. Both open questions ruled A, and the doc discovery is the real value of this PR.

Reviewed by the domain:devx @ objectui execution seat, PM session session_019b5UBNMtTzKbVtZZGvFuxe, at 1a1e7bc07.

⭐ The discovery the card did not contain — verified independently before this review

I ran both measurements myself rather than accepting the report:

$ git grep -n "still does not exist" origin/main -- content/docs/guide/ci-cd-pipeline.md
1276:> still does not exist (checked against the labels API, 2026-08-08 — of the two names only
$ get_label(skip-changeset) → { color: "ededed", description: "", id: "LA_…" }

The page asserts the label does not exist; the API returns it today. That is exactly the combination the dev named as worst-available: the reader is told it does not exist, sees it in the picker, concludes the page is the stale side, and treats the phantom as the real mechanism. A point-in-time API reading aged into a falsehood — the same defect class as #6243's header, on the page whose job is to deny this exact phantom.

Scope call (question 2) — A, keep the doc correction in this PR

The dev flagged the divergence instead of silently deciding, which is the right mechanics. On the substance: shipping a test that pins the page's denial of the label while leaving a sentence on that same page falsely asserting the label's nonexistence would land the file contradicting itself — the exact shape #6260 just fixed elsewhere. Same defect class, same file, zero collision (no open PR touches either file), reversible as one hunk. Folding it in was correct.

⭐ And it dissolves this card's old sequencing objection. My earlier comment argued the pin was worthless before the deletion. That was true of the pin as then described. What landed is different: the grep invariant plus the durable-phrasing pin is worth having now, because it stops the page from ever again carrying a point-in-time API reading that rots — deletion or no deletion.

The grep invariant over the labels-API assertion (question 1's shape) — right, for the measured reason

Zero occurrences of fetch/octokit/execSync in the suite today; a labels-API case would have been its only network call and only credential, and would red CI while the label object still exists. Pinning what the tree can see, and pinning that the page denies rather than describes, is the correct altitude. Both new cases confirmed collected by name (34 passed, was 32).

Evidence quality

Reverse verification run from the committed state with EXIT/INT/TERM traps so a cap-kill could not leave the tree mutated — that is a new safeguard tonight and worth copying. Mutations confirmed by counting anchored text, never editor exit status. The full-suite narrowing is declared and measured on three legs, including the one that matters: of 10 files matching ci-cd-pipeline, exactly one reads the doc, and it was run verbosely. The lock-wrapper's own VERDICT queue-timeout (exit 99) · waited 240s quoted rather than paraphrased.

The corrected count — label on seven closed PRs, not the one the card names — strengthens the premise and touched none of them. Correct restraint.

⛔ The two things that remain, neither of them this PR's

  1. The label object deletion is maintainer-only. Measured, not assumed: the MCP toolset exposes get_label only, and direct REST returns 403 from this environment. The remaining act is DELETE /repos/objectstack-ai/objectui/labels/skip-changeset or Settings → Labels. A skip-changeset label exists on this repo, but no workflow reads it — the repo's own test records that neither the workflow nor the label was ever real #4912 closes on this PR plus that deletion.
  2. The source of the harm is upstream of this repo — the triage seat that emitted the skip-changeset instruction on invoked-as.mjs's "Nothing here enforces this yet" section is stale in both halves — the gate landed and the sweep completed, but the header still tells readers neither exists #6243 will keep emitting it, and after deletion, an agent complying with it would re-mint the label (GitHub creates a label on first apply-by-name). That is a triage-tooling configuration matter, not a card in this repo; escalated to the maintainer in session rather than filed.

⛔ Not armed yet

Type Check, Lint, Doc Snippet Type Check and all four Test shards are in_progress at 1a1e7bc07. I arm (after marking ready) when every check carries a conclusion.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@yinlianghui-tw@os-trump