Skip to content

check-node-esm-load.mjs's readTsconfig() destroys any tsconfig whose JSON strings contain /*, so effectiveNoEmit() silently answers from its catch block for packages/auth and packages/fields #5367

Description

@os-support-ai

Found while clearing the six small packages of #5214, by running the gate's own exported helpers directly. Not fixed there — it is gate machinery, not the defect that card is about, and the direction of the failure is currently safe.

What was measured, 2026-08-20

import{readTsconfig,effectiveNoEmit}from'./scripts/check-node-esm-load.mjs';readTsconfig('packages/auth/tsconfig.json')// SyntaxError: Expected ':' after property name in JSON at position 182readTsconfig('packages/fields/tsconfig.json')// SyntaxError: Expected ',' or '}' after property value in JSON at position 87

readTsconfig strips comments with two regexes over the raw text:

.replace(/\/\*[\s\S]*?\*\//g,'')// block comments.replace(/^[\t]*\/\/.*$/gm,'')// whole-line comments

Neither knows what a JSON string is. packages/auth/tsconfig.json contains

"paths": { "@/*": ["src/*"] },
..."exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.test.tsx"]

The /* inside "@/*" opens a "block comment" that runs to the */ inside "**/*.test.ts" at the bottom of the file, eating paths, include and most of exclude. What survives the strip is:

 "paths": {
"@*.test.ts", "**/*.test.tsx"]
}

packages/fields/tsconfig.json loses everything after "outDir": "dist" the same way, via the packages/*/src written inside one of its // comments.

Why nothing is red today

effectiveNoEmit catches the parse failure deliberately:

}catch{// An unreadable config is graded as emitting, so a parse failure widens the// scan rather than silently dropping a package out of it.returnfalse;}

So both packages are graded "emitting", buildPreservesSpecifiers returns true, and they land inside the ratchet's scope — which is where they belong. The gate's verdict is right; it just is not being reached by the code that is supposed to decide it.

Why it is still worth fixing

  • The conservative catch is the documented fallback for an unreadable config. It is silently absorbing a readable one, so the safety margin the comment describes is already spent, and nobody can see it.
  • packages/fields is the case where the answer actually matters and is actually wrong: its tsc step inherits the root's noEmit: true (it only type-checks; dist comes from vite). Correctly parsed, effectiveNoEmit returns true and fields would drop out of the specifier leg. Measured alongside Seven published packages still emit extensionless relative specifiers, so plain Node ESM cannot load them #5214: fields' dist carries 0 extensionless relative specifiers even before that card's fix — its published entry failed via @object-ui/providers, never on its own file. So the gate is currently ratcheting fields' sources on the strength of a verdict it never computed. (Whether it should keep ratcheting them is a real question and probably yes — but it should be a decision, not an accident.)
  • Any future package whose tsconfig genuinely should be excluded will be included instead, with no signal.

Suggested shape

Parse with a real JSONC reader rather than regexes — TypeScript itself ships ts.parseConfigFileTextToJson, and the repo already depends on typescript. If a dependency-free strip is preferred, it has to be string-aware: track " and skip comment openers inside strings.

Whatever the fix, it should come with the assertion this class of bug needs: that readTsconfig round-trips every tsconfig in the repository without throwing. That is the check that would have caught it, and it is cheap.

Found via #5214.

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repopm:queuetooling

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions