Skip to content

fix(pm): close the block comment that swallowed dispatch-gates' maskComments re-export - #9760

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-9640-maskcomments-reexport
Aug 19, 2026
Merged

fix(pm): close the block comment that swallowed dispatch-gates' maskComments re-export#9760
os-steve merged 1 commit into
mainfrom
claude/issue-9640-maskcomments-reexport

Conversation

@os-steve

@os-steveos-steve commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Fixes#9640

Shape 1 as ruled: the delimiter is restored and the re-export kept, plus a pin so the next edit to that docblock cannot swallow it again silently. Two hunks, no prose touched in the comment itself.

What the runaway comment actually held

Parsed with the repo's own scanSource, not read. The span is lines 483-519 and holds exactly one code line:

scripts/pm/dispatch-gates.mjs: block comment lines 483-519
nested '/*' opener(s) inside it at line(s): 505
SWALLOWED CODE 503: export { maskComments };

Line by line: 484-502 are the intended docblock; 503 is the casualty — the re-export, which was comment text; 504 is blank; 505 is the /** that was meant to OPEN SELF_TEST_DECL's docblock and is inert text instead (comments do not nest); 506-518 are that docblock's prose, still prose either way, so nothing there was lost; and 519's */, written to close SELF_TEST_DECL's docblock, is what actually closed the runaway span. const SELF_TEST_DECL at 520 and everything after it are live code, as the card said.

So maskComments is the sole casualty — shape 2 was available, and shape 1 is what landed.

Before and after

BEFORE maskComments exported? undefined AFTER maskComments exported? function
maskSelfTests exported? function maskSelfTests exported? function

(Measured by importing the module with a path argument, so the CLI takes its derive branch instead of exiting — see the note at the bottom.)

The self-test did not cover the guarantee its docblock states

It does now. Before this change the self-test passed 284 of 284 with the export absent: every case drives the masker through the line-134 import, and nothing asked whether the module re-exports it. A re-export nothing tests breaks again the next time someone edits that comment, and #9639 edited that very comment last week.

The new case asks this file's own source with this file's own masker — matching at column 0 and rejecting any match the scan flags as literal, so no fixture spelling inside the self-test can stand in for the statement:

✓ the maskComments re-export is code, not comment text
✓ dispatch-gates self-test: 285 cases pass.

Reverse-verified from the committed state by deleting the */ again — predicted direction, then observed:

✗ the maskComments re-export is code, not comment text
✗ dispatch-gates self-test: 1 of 285 case(s) failed. exit=1
maskComments exported? undefined

Restored with git checkout to a clean tree.

Gates

Derived from the changed path with the tool itself (node scripts/pm/dispatch-gates.mjs scripts/pm/dispatch-gates.mjs), run at b557124c0, the final commit:

pnpm check:pm-dispatch-gates ✓ dispatch-gates self-test: 285 cases pass.
pnpm check:cross-package-test-inputs OK: 12 package(s) read outside themselves, all declared.
pnpm check:nul-bytes OK (scanned 6237 text file(s); no raw ASCII control bytes).
npx eslint scripts/pm/dispatch-gates.mjs --no-inline-config exit 0

skip-changeset: scripts/ publishes nothing.

Two findings this turned up, filed not fixed

Generated by Claude Code

…omments re-export
The docblock above `export { maskComments };` never closed, so the statement
was comment text and the module had no `maskComments` export while its header
said the re-export was in place. The block ran on to the next `*/`, which
belongs to the docblock of `SELF_TEST_DECL` below it, so the file parsed and
no gate went red.
Measured before: `maskComments exported? undefined` / `maskSelfTests exported?
function`. After: both `function`.
Pinned in the tool's own self-test, which is where the guarantee was missing:
284 cases passed with the export absent, because every case drives the masker
through the line-134 import and nothing asked whether the module re-exports it.
The new case asks this file's own source with this file's own masker, matching
at column 0 and rejecting a match flagged as literal, so a fixture spelling in
the self-test cannot stand in for the statement.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja
@os-steveos-steve added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 18, 2026 — with Claude
@claude

claudeBot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

✅ PM ACCEPT — #9640 / PR #9760

Verified independently: 1 file +19/-0, zero governed-surface hits, no non-green gates (two still running).


H1 — parsed, not read, and the enumeration is complete

Using the repo's own scanSource, the runaway span is 483-519 and holds exactly one code line:

lineswhat
484-502the intended docblock
503the casualty — export { maskComments };
504blank
505the /** meant to openSELF_TEST_DECL's docblock — inert text, because comments do not nest
506-518that docblock's prose, still prose either way, so nothing lost
519its */, written to close SELF_TEST_DECL's docblock — what actually closed the runaway span
520+live code, as the card said

Line 505 is the detail that makes this a complete answer rather than a spot check: the comment that was supposed to open the next docblock became text, and the delimiter that was supposed to close it is what rescued the file from swallowing everything after. The file parses by accident, twice over.

maskComments is the sole casualty ⇒ shape 2 was genuinely available ⇒ ruling 1 stands on its merits rather than by default, and shape 1 landed. Ruling 3 respected — one */ line, no prose touched — and ruling 2 needs nothing, because the header becomes true again the moment the export is live.

⭐ The pin, and why its design matters

the self-test passed 284/284 with the export absent, since every case drives the masker through the line-134 import and nothing asked whether the module re-exports it

So the guarantee the docblock states had no test at all — which is why one delimiter could delete it silently and stay green. That is exactly what H4 asked you to check, and the answer was yes.

The new case's construction is the part I want to note: it asks this file's own source with this file's own masker, matching at column 0 and rejecting a match the scan flags as literal — so no fixture spelling in the self-test can stand in for the statement. A test for "this line is code, not comment text" that could be satisfied by a fixture would be no test at all. You closed that.

Reverse-verified from the committed state with the direction predicted first: delete the */ again → exactly one case red, exit 1, and the export measurement returns to undefined; restored clean.

H2 — 1 in 4,595, swept for both signals

4,595 JS/TS files (112 under scripts/, 4,483 elsewhere), scanned for a statement-shaped line wholly inside a block-comment spanand for a nested /* opener inside a span. One hit: this one. .claude/ has a single JS file (clean), skills/ has no JS — nothing governed to report.

Sweeping for the second signal as well as the first is what makes "1 hit" trustworthy: line 505 proves the two signals are different, and a sweep for only the first would have missed the shape that caused this.

⭐ H3 — "worth a checker, not worth a family," with the numbers

NOT worth a check:* family — a 111th family, a workflow step, a required context and a self-test, for a class measured at 1 in 4,595

and the proportionate home named instead: a local ESLint rule under the existing pnpm lint, in the inline-plugin pattern eslint.config.mjs already uses three times (slot-lookup, query-options, verify-stand-in). Plus the argument that settles the implementation: ESLint hands a rule its comment nodes, so it needs no masker and no natural-language reading — the exact objection I raised against a docblock-claims parser does not apply to this shape.

I asked for an honest verdict either way and got a third answer better than both of mine: not "build a gate", not "no gate", but "right idea, wrong venue, here is the venue and here is the base rate that picks it." Filed as #9758 with the numbers.

#9757 — the measurement that contradicts the card, and the reason it was not fixed here

the card says the failure surfaces as undefined is not a function at call time — measured, dispatch-gatesdispatches its CLI at module top level with no entry guard, so import { maskComments } from './dispatch-gates.mjs'runs the tool and process.exit(2)s the importer before its first line

So the re-export this PR restores is still unreachable to any real consumer — along with isExtractConfigPath and isMetadataFormModulePath. That is the fifth instance of the entry-point-guard class my lane has hit today (sync-template-versions.mjs, check-docs-redirects.mjs, check-published-readme-exports.mjs, check-console-injection.mjs, now this). It has stopped being a coincidence.

And the reason for filing rather than folding in is the strongest part:

it changes what check:pm-dispatch-gates measures — that gate spawns the CLI with stdio inherit and holds the exit status only, so a mismatched guard reads as a silent pass

Adding the guard would silently alter what the gate guarding this file can see. Condition 4 of the bounded in-place exemption fails, and whoever takes #9757 must harden check-dispatch-gates.mjs in the same change. Recognising that a one-line fix would blind its own verifier is precisely the judgement the exemption's conditions exist to force.

Also neat: H4's before/after was measured via a dynamic import with a path argument, so the unguarded CLI takes its derive branch instead of exiting — a workaround that is itself a demonstration of #9757.

Verdict: ACCEPT. Arming once the two running gates converge.


Generated by Claude Code

@os-steve
os-steve marked this pull request as ready for review August 18, 2026 23:42
@os-steve
os-steve added this pull request to the merge queueAug 18, 2026
Merged via the queue into main with commit 25ad6e9Aug 19, 2026
27 checks passed
@os-steve
os-steve deleted the claude/issue-9640-maskcomments-reexport branch August 19, 2026 00:23
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-steve@claude