LearnNewsExamplesServices
Frontmatter
id19087
titlebuild-all exits 1 for an engine built inside node_modules/neo.mjs
stateClosed
labels
bugairegressionbuild
assigneesneo-opus-grace
createdAtSep 23, 2026, 5:10 AM
updatedAtSep 23, 2026, 10:59 AM
githubUrlhttps://github.com/neomjs/neo/issues/19087
authorneo-opus-grace
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[ ] 19047 v13.2 cannot be deployed: pages pins one engine version and devindex has no home
closedAtSep 23, 2026, 10:59 AM
milestonev13.2

build-all exits 1 for an engine built inside node_modules/neo.mjs

Closed v13.2.0/archive-v13-2-0-chunk-22 bugairegressionbuild
neo-opus-grace
neo-opus-grace commented on Sep 23, 2026, 5:10 AM

Context

Found by the C1 content pre-read for #19047 AC-4 (receipt). neomjs/pages deploys by running npm run build-all with cwd: node_modules/neo.mjs (pages/buildScripts/updateNeoVersion.mjs:154-158) and exits on any non-zero status. With the engine packed from dev@526b65e0ee, the dist/esm step fails:

dist/esm: 3214 import(s) still address the workspace engine at node_modules/neo.mjs:

Every import in the tree is flagged, ./ siblings included: src 1,344, examples 1,090, apps 730, docs 47, ServiceWorker.mjs 3.

The guard came in with #17921 / #17932 (34499f737b, 2026-08-31). No release tag contains it, so 13.1.0's deploy never ran it. v13.2's deploy will.

The Problem

findUnresolvableImports (buildScripts/util/esmDistTransforms.mjs:450) fails an import when addressesSourceEngine(resolved) is true, and resolved is the import target's absolute path (buildScripts/build/esmodules.mjs:165). addressesSourceEngine matches any path containing /node_modules/neo.mjs/.

  • In a workspace, that segment appears only when an import reaches the workspace's own engine: the two-graph defect the guard exists for.
  • In an engine built in place inside a consumer's node_modules/neo.mjs, the build root itself contains the segment, so every resolved path does.

The check is keyed to where the build runs, not to where the import points.

The Architectural Reality

  • esmodules.mjs sets root = path.resolve() (the cwd) and derives insideNeo from the package name. Pages' flow is insideNeo: true with root <pages>/node_modules/neo.mjs.
  • The guard's second arm, addressesSourceEngine(specifier), reads the specifier as written and does not depend on the root.
  • The resolved-path arm still earns its place. It catches a specifier that names the engine only after normalisation (node_modules/./neo.mjs/…). The text arm misses that form. rewriteImportPaths does too, because it keys on the literal /node_modules/neo.mjs/, so it leaves the import pointing at the workspace engine.
  • test/playwright/unit/buildScripts/esmDistTransforms.spec.mjs pins the pure function. esmWorkspaceBuild.spec.mjs drives the real esmodules.mjs over synthetic workspaces. None of those workspaces sits under a node_modules/neo.mjs path, which is why no spec saw this.

Measured A/B, same in-place tree (3,503 emitted modules): the 526b65e0ee guard exits 1 with 3,214 flagged. Judging the resolved path relative to the build root exits 0 in 3.35 s. With that one change, pages' full build-all exits 0 in 36 s.

The Fix

  • findUnresolvableImports(modules, exists, resolve, root) judges the resolved-path arm on path.relative(root, resolved). The root's own location is never evidence. Everything below and beside it is judged as before, so a hoisted ../node_modules/neo.mjs/… still matches.
  • esmodules.mjs passes its root.
  • Specs:
    • esmDistTransforms: an in-place sibling import reports nothing (red on today's code).
    • A control: an in-place import that really names node_modules/neo.mjs still fails as engine-identity.
    • A normalisation arm that pins why the resolved-path arm stays.
    • esmWorkspaceBuild: the real script, with cwd under …/node_modules/neo.mjs, exits 0 (red today).

Acceptance Criteria

  • AC-1: An engine built in place inside a node_modules/neo.mjs directory builds dist/esm with exit 0 when its imports resolve. A real-script arm pins this and reds on today's code.
  • AC-2: Identity is still enforced for such a build and for workspaces. An import that addresses node_modules/neo.mjs below or beside the root fails as engine-identity, including one that names it only after normalisation.
  • AC-3 (receipt): pages' layout (main@1847ca65, engine packed from the fix head) runs build-all past the dist/esm step. Any later failure is reported on #19047 as a defect of its own.

Out of Scope

  • The rest of #19047 AC-4: topology, devindex, and regenerating the portal indexes.
  • Containment for in-place builds: an emitted import reaching the root's own src/ outside dist/esm. It is not flagged before this change or after it.

Related

  • #19047 — this blocks its AC-4.
  • #17921 / #17932 — where the guard came from. #17942 — its computed-specifier arm.

Sweeps

  • Live latest-open sweep: latest 20 open issues at 2026-09-23T03:08Z. No equivalent.
  • A2A in-flight sweep: last 15 inbox messages, all read-states. No claim on this scope.
  • MC sweep: dist/esm build engine-identity addressesSourceEngine node_modules/neo.mjs false positive build root inside node_modules pages build-all. 6 results, no prior decision.
  • Own-assignment sweep: 9 open, none overlapping.
  • Keyword sweep: still address the workspace engine, addressesSourceEngine, engine-identity dist/esm. Hits are only #17921 (closed, the origin) and #19047.

Decision Record impact: none. Structure-map gate: N/A (buildScripts/, not ai/).

Origin Session ID: 19051047-2f38-4bf0-be51-efd6b957564d

Retrieval Hint: query_raw_memories("dist/esm engine-identity guard engine built in place node_modules/neo.mjs pages build-all")

Authored by 🖖 Grace · @neo-opus-grace · Claude Opus 5.5 · Claude Code

tobiu referenced in commit 9c5b5f9 - "fix(build): the dist/esm identity guard judges paths below the build root (#19087) (#19088) on Sep 23, 2026, 10:59 AM
tobiu closed this issue on Sep 23, 2026, 10:59 AM