Skip to content

[finding] dispatch-gates' export { maskComments } is swallowed by an unterminated block comment — the docblock claims a re-export the module does not have #9640

Closed

Description

@os-steve

Noticed while measuring the hint scan for #9626 (PR #9639). Not fixed there: different defect class, and the correct shape is a judgment call rather than mechanical.

What happens

scripts/pm/dispatch-gates.mjs line 483 opens a block comment that is never closed before the statement it introduces:

483: /**
484:  * The comment/literal scanner used to live here. It now lives in
...
501:  * Re-exported because this tool's self-test drives the SAME masker the gates
502:  * run, not a copy of it.
503: export { maskComments };

There is no closing delimiter between line 502 and line 503, so export { maskComments }; is comment text. The block runs on until the next closing delimiter, which belongs to the docblock of SELF_TEST_DECL below it — that constant and everything after it are fine, so the file parses and nothing is red.

Measured:

maskComments exported?  undefined
maskSelfTests exported? function

Why it is worth recording

The docblock states the reason the re-export exists — "Re-exported because this tool's self-test drives the SAME masker the gates run, not a copy of it" — and that reason is exactly the drift this file's whole contract is written against (scripts/js-comment-mask.mjs's header records five gates that each grew a private copy of this judgment and got it wrong two different ways). The header says the guarantee is in place; the module does not provide it.

Dormant today, in one specific sense only: nothing currently imports maskComments from dispatch-gates. Every live consumer imports it straight from scripts/js-comment-mask.mjs (check-dispatcher-error-vocabulary, check-platform-checklist, check-examples-live-imports, docs-audit/affected-docs, check-org-identifier, check-error-code-casing, check-error-status-conformance), and dispatch-gates' own internal use works because it imports the symbol at line 134. So the failure surfaces only when someone follows the docblock and writes import { maskComments } from './dispatch-gates.mjs' — which fails as undefined is not a function at call time, not at import.

Shapes

  1. Close the comment before line 503 and keep the re-export. Restores what the docblock promises; costs one delimiter.
  2. Delete both the re-export line and the paragraph promising it, and let consumers import from js-comment-mask.mjs — which is what every consumer already does, and arguably the better direction, since a re-export through a 3000-line CLI is a longer path to the same function.

Triage's call which. Note that maskSelfTests next door IS a real export function, so whichever way this goes, the two neighbours should stop looking symmetric when they are not.

A guard would have caught it

The class is "a statement that reads as code but is inside a comment". Nothing in the tree looks for it. Whether that is worth a gate is a separate question from this one-line repair; recording it here rather than assuming.

Filed unassigned, no pm:queue — an observation, triage's call.


Generated by Claude Code

Activity

  1. added
    toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface
    domain:devxDomain lane — seat card indexed by label:pm:seat
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 18, 2026
  2. removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 18, 2026
  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 18, 2026
  4. os-steve commented on Aug 18, 2026

    @os-steve
    CollaboratorAuthor

    Claiming this card.

    Scope: scripts/pm/dispatch-gates.mjs (the runaway block comment) plus whatever H1's enumeration turns up inside it. Will report H1 enumeration, H4 before/after measurement, H2 repo-wide sweep, and an H3 verdict on a contained gate.


    Generated by Claude Code

  5. os-steve commented on Aug 18, 2026

    @os-steve
    CollaboratorAuthor
    {
      "issue": 9640,
      "status": "done",
      "branch": "claude/issue-9640-maskcomments-reexport",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/9760",
      "premise_still_valid": true,
      "summary": "H1 (leads): parsed with the repo's own scanSource, the runaway span is lines 483-519 and holds exactly ONE code line — 503 `export { maskComments };`. Enumerated: 484-502 the intended docblock; 503 the casualty; 504 blank; 505 the `/**` that was meant to OPEN SELF_TEST_DECL's docblock and is inert text instead (comments do not nest); 506-518 that docblock's prose, still prose either way, so nothing was lost there; 519's `*/`, written to close SELF_TEST_DECL's docblock, is what actually closed the runaway span; `const SELF_TEST_DECL` at 520 onward is live code, as the card said. maskComments is the sole casualty, so shape 2 was genuinely available — ruling 1 stands and shape 1 landed. Fix: one ` */` line before 503, no prose in that comment touched (ruling 3), and the header needed no correction because it is true again once the export is live (ruling 2). Plus a pin, because the guarantee had no test: 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. The new case 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. H2: swept 4,595 JS/TS files (112 under scripts/, 4,483 elsewhere) for both signals — a statement-shaped line wholly inside a block-comment span, and a nested `/*` opener inside a span; 1 hit, this one. `.claude/` has one JS file (clean), `skills/` has no JS, so nothing governed to report. H3 verdict: worth a checker, NOT worth a check:* family (111th family, workflow step, required context and self-test for a class measured at 1 in 4,595) — the proportionate home is 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); ESLint hands a rule its comment nodes, so it needs no masker and no natural-language reading. Filed as #9758 with the numbers rather than built here. One measurement contradicts the card and is bigger than the delimiter, but not the way H1 anticipated: the card says the failure surfaces as `undefined is not a function` at CALL time — measured, dispatch-gates dispatches 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. Filed as #9757 rather than fixed here: 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), which is a second file, a second defect class and a new verification surface — condition 4 of the bounded in-place exemption fails.",
      "tests": "All at final commit b557124c0 (tree clean, nothing later). H4 before, on main: `maskComments exported?  undefined` / `maskSelfTests exported? function` — reproduced exactly as the card measured, via a dynamic import with a path argument so the unguarded CLI takes its derive branch instead of exiting. H4 after: `maskComments exported?  function` / `maskSelfTests exported? function`. Self-test before: `284 cases pass` WITH the export broken (the card's guarantee was untested); after: `the maskComments re-export is code, not comment text` / `dispatch-gates self-test: 285 cases pass`. Reverse verification ran from the COMMITTED state, direction predicted first (exactly one case red, exit 1) and observed: after deleting the `*/` again, `dispatch-gates self-test: 1 of 285 case(s) failed.` exit=1 and the export measurement returned to undefined; restored with `git checkout branch -- path` to a clean tree (no build products involved — this is a script edit, so no dist rebuild applies and no package build was needed). Gate union derived from the actual changed path with the tool itself (`node scripts/pm/dispatch-gates.mjs scripts/pm/dispatch-gates.mjs` names check:pm-dispatch-gates and check:cross-package-test-inputs), run at b557124c0: `check:pm-dispatch-gates` 285 cases pass; `check:cross-package-test-inputs` OK, 12 packages read outside themselves, all declared; `check:nul-bytes` OK, 6237 text files scanned, no raw ASCII control bytes; `npx eslint scripts/pm/dispatch-gates.mjs --no-inline-config` exit 0. PR labels read back after the write: size/s, skip-changeset.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #9757: scripts/pm/dispatch-gates.mjs dispatches its CLI at module top level with no entry guard, so importing it runs the tool and exits the importer — the docblock's re-exports (maskComments, and isExtractConfigPath/isMetadataFormModulePath) are unreachable to any consumer even after this fix; precedent #6566 to PR #6695; whoever takes it must also harden check-dispatch-gates.mjs, which holds the exit status only",
        "filed as #9758: no checker exists for the class 'a statement swallowed by an unterminated block comment' — H2 base rate 1 in 4,595 files, verdict is a local ESLint rule rather than a new check:* family, with the proposed rule shape and the reasoning both ways"
      ]
    }

    Generated by Claude Code

  6. removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:devxDomain lane — seat card indexed by label:pm:seattoolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions