Repository navigation
emit-eager-closure-report crashes with ENOENT when a fully-unresolvable dependency lands in the vendor-objectstack chunk #5996
Description
Observation, found while reproducing #5391's late-failure repro. Not filed against a specific consumer; not blocking anything currently known — filing so triage can rank it.
What I measured
Reproducing #5391 (injecting a spec dist with no reachable node_modules via OBJECTSTACK_SPEC_DIST, before that issue's fix) surfaced two of the override's declared dependencies unresolvable: zod and pg-connection-string. On this repo's current toolchain (vite@8.2.1 + rolldown@1.2.3, base d8b48f495), the two failed differently through the SAME real apps/console vite build:
zod— silently kept as an unresolved bareimport ... from "zod"in thevendor-objectstackchunk. No warning, no error, transform reports8613 modules transformedand moves on.pg-connection-string— appears to become its own "chunk" entry in the Rollup/Rolldown output bundle map (type: "chunk",fileName: "pg-connection-string", no extension), but that chunk's file is never actually written todist/.apps/console/vite.config.ts'semit-eager-closure-reportplugin (writeBundlehook) walks the eager closure and callsfs.readFileSync(path.join(outDir, fileName))for every chunk it finds — including this one — and crashes:
error during build:
Build failed with 1 error:
[plugin emit-eager-closure-report]
Error: ENOENT: no such file or directory, open '.../apps/console/dist/pg-connection-string'
at Object.openSync (node:fs:560:18)
at Object.readFileSync (node:fs:444:35)
at .../vite.config.ts...:519:20 (emitEagerClosureReport's writeBundle)
Confirmed the underlying chunk really is empty/unwritten: find dist -iname "*pg-connection*" returns nothing after the build, even though the bundle map handed to writeBundle names it.
Why it's worth a line
emitEagerClosureReport's own header documents two counter-probes specifically so an under-counting walk fails loudly rather than silently reporting a small number — real care was taken here. This is a gap the same spirit should probably close: the walk assumes every chunk in bundle with type === 'chunk' was actually written to outDir, which is not true for at least one shape of externalized/unresolvable import. The crash IS loud (in that sense it's the "good" failure direction the module's header cares about), but the message names neither the real cause (an unresolvable bare import reaching the eager closure) nor points at emit-eager-closure-report's own assumption — it reads as a generic Node ENOENT, several frames removed from the actual problem.
Scope note
Not reachable via #5391's own path any more once that issue's fix lands (the spec-dist override's own dependencies are now validated before the build ever starts). Still reachable by any other future eager-closure member that ends up genuinely unresolvable for other reasons — e.g. the OBJECTSTACK_CLIENT_DIST sibling hook, or a future advancedChunks group misconfiguration.
Possible shapes (not a recommendation)
- Guard the
fs.readFileSyncinemitEagerClosureReportwith an existence check, andthis.error()with a message naming the missing chunk file and suggesting an unresolved import is the likely cause — turning this from a bareENOENTinto a self-diagnosing failure, matching the two existing counter-probes' spirit. - Or: investigate why Rolldown emits a chunk entry for
pg-connection-stringat all rather than folding it into the importing chunk as a plain external — that may point at aadvancedChunks.groupstest unintentionally matching bare external specifiers.
Related: #5391 (where this was found), #5324 / #5266 (the eager-closure report's own history).
Activity
yinlianghui-tw commentedon Aug 24, 2026
PM claim + dispatch order — R13, with the shape ruled
Claimed by the
domain:devx@ objectui execution seat (#5748), round R13, PM sessionsession_019b5UBNMtTzKbVtZZGvFuxe. Branch:claude/issue-5996-eager-closure-missing-chunk.Ruling: take shape 1. Shape 2 is investigation — report it, do not fix it.
The card offers two and recommends neither. Ruled:
Shape 1 — guard the
fs.readFileSyncinemitEagerClosureReportwith an existence check, andthis.error()with a message that names the missing chunk file and says an unresolved bare import is the likely cause. That turns a bare NodeENOENTseveral frames from the real problem into a self-diagnosing failure, which is the spirit the module's own header already establishes with its two counter-probes.Shape 2 — why Rolldown emits a chunk entry for⚠️ Time-box this and report what you find; do not change chunking configuration. Touching
pg-connection-stringat all rather than folding it into the importing chunk as a plain external, and whether anadvancedChunks.groupstest is unintentionally matching bare external specifiers.advancedChunksalters what ships to every user, and it would be a much larger change than this card's premise supports. If the investigation is inconclusive, saying so is a complete answer — file it as a follow-up card rather than guessing.The card says this is "not reachable via #5391's own path any more once that issue's fix lands". PR #5995 (card #5391) merged today, so that path is closed. Confirm that on current
origin/main, then reproduce the crash by another route — the card names theOBJECTSTACK_CLIENT_DISTsibling hook as one.Verification
ENOENTfirst, then your named error. A fix never shown to change a failure is not verified.writeBundleduring a realvite build. State how you rebuilt and how you know the artifact under test is the one you edited. This lane lost a round to a stale artifact today.⛔ Out of scope:
advancedChunksconfiguration, the spec-dist override validation (#5391, landed), and the eager-closure report's counting logic (#5324 / #5266).⛔ Never
git stash— shared stack, other agents are using it now.Generated by Claude Code
yinlianghui-tw commentedon Aug 24, 2026
{ "issue": 5996, "status": "done", "branch": "claude/issue-5996-eager-closure-missing-chunk", "pr": "https://github.com/objectstack-ai/objectui/pull/6095", "premise_still_valid": true, "summary": "Re-derived the premise first: b0de7a85c (PR #5995, card #5391) IS on origin/main, so that route is closed — but the crash reproduces exactly through the OBJECTSTACK_CLIENT_DIST sibling hook the card named, which #5995 did not touch (it is a plain Vite string alias with no dependency validation). Reproduction: copy the installed @objectstack/client dist out of the workspace and point the hook at it; its three bare imports become unresolvable and the plugin dies on `ENOENT ... dist/@objectstack/core/logger`. Shape 1 implemented as ruled: the fs.readFileSync in emitEagerClosureReport is guarded by an existence check that this.error()s naming the missing file, the chunk that imports it, and the unresolved-bare-import diagnosis; the failure stays a build failure, only the message changed. Shape 2 investigated, nothing configured: the card's stated mechanism is FALSIFIED — rolldown does not emit a chunk entry for the specifier (inBundleMap false; 515 bundle keys = 508 chunks + 7 assets, none of them the specifier). It appears only inside assets/framework-*.js's `imports` array, because rolldown lists a chunk's EXTERNAL imports there beside real chunk file names, and the walk does eager.add() without asking chunks.has(). No advancedChunks.groups test matches these ids (no /node_modules/ and no /@objectstack+ segment), so the group-test suspicion is NOT supported. IMPORTANT CAVEAT for the PM: under the real console plugin stack neither the old ENOENT nor the new message reaches the developer — viteIneffectiveDynamicImports()'s closeBundle counter-probe replaces the reported build error whenever the build died earlier, so all the user sees is `43 pinned ineffective dynamic import(s) did NOT fire`. That masking is pre-existing, is a different plugin (#5325's ledger), and is filed as #6093; this fix is correct and demonstrated but stays invisible to developers until that lands.", "tests": "All on committed head b3e278420, worktree /home/user/objectui-5996, heavy commands serialized through scripts/pm/os-verify-lock.sh. PAIRED BEFORE/AFTER on one reproduction, configs differing by exactly this PR's diff (each generated by one identical sed removing viteIneffectiveDynamicImports(), which otherwise masks both errors). BEFORE: 'Build failed with 1 error: [plugin emit-eager-closure-report] Error: ENOENT: no such file or directory, open .../apps/console/dist/@objectstack/core/logger'. AFTER: 'Build failed with 1 error: [plugin emit-eager-closure-report] RolldownError: [emit-eager-closure-report] eager-closure member `@objectstack/core/logger` has no file in `.../apps/console/dist`, so its bytes cannot be weighed. It is almost certainly an UNRESOLVED BARE IMPORT ... (imported by: assets/framework-Bwcc-tp7.js) ...'. ARTIFACT FRESHNESS: vite compiles the config into .vite-temp/<config>.timestamp-<ms>-<rand>.mjs freshly per invocation — the two runs name different files in their stack traces (before.ts.timestamp-1787588787207-3d31ebadb622b.mjs vs after.ts.timestamp-1787588797390-b421aa65b219f.mjs), and the after text exists nowhere in the repo but this diff. NON-VACUITY: real vite.config.ts, no override, on b3e278420 exits 0 with '[plugin emit-eager-closure-report] eager closure: 52/508 chunks, 3298242 bytes gzipped (3220.9 KB)' — byte-identical to the pre-fix baseline on 7c96c9420, and grep -c of the guard text in that log is 0; measured reason it is silent: the only externals on a normal build are module/fs/path from @objectstack/lint, which sits behind the lazy boundary. GATES: `pnpm --filter @object-ui/console type-check` exit 0 (tsc --noEmit && tsc -b tsconfig.node.json --force; tsconfig.node.json is the project containing vite.config.ts) — the first run's 352 TS2882 errors were the unbuilt-dependency-closure trap, none naming vite.config.ts, and green after `pnpm --workspace-concurrency=2 --filter '@object-ui/console^...' build`. `pnpm --filter @object-ui/console lint` exit 0 ('203 problems (0 errors, 203 warnings)', all pre-existing). Root vitest on the 6 config-adjacent suites: 'Test Files 6 passed (6)' / 'Tests 157 passed (157)'. `node scripts/check-changeset-presence.mjs`: 'No source of a released package changed in this range, so no changeset is owed.' DECLARED NARROWING: the repo-wide suite and the full lint farm were not run locally; CI owns that verdict. All exit codes captured before any pipe (redirect-then-capture), and every verdict above is the gate's own printed line.", "open_questions": [], "out_of_scope_findings": [ "filed as #6093: ineffective-dynamic-import-ledger's closeBundle counter-probe replaces the real error whenever an earlier console plugin fails the build — measured, the ENOENT/guard message appears 0 times under the real plugin list", "filed as #6094: OBJECTSTACK_CLIENT_DIST has no dependency-resolution validation (the asymmetry with OBJECTSTACK_SPEC_DIST after #5995), so an out-of-tree client override silently produces a bundle with bare specifiers no browser can load" ] }Generated by Claude Code
yinlianghui-tw commentedon Aug 24, 2026
os-dev-report
(Re-post of the comment directly above. The HTML-comment marker on that one was stripped by GitHub's body sanitizer after it landed, which makes it invisible to a scan keyed on the marker — so this copy leads with the literal text instead. Same payload, no changes; the earlier comment is the stripped duplicate, not a second report.)
{ "issue": 5996, "status": "done", "branch": "claude/issue-5996-eager-closure-missing-chunk", "pr": "https://github.com/objectstack-ai/objectui/pull/6095", "premise_still_valid": true, "summary": "Re-derived the premise first: b0de7a85c (PR #5995, card #5391) IS on origin/main, so that route is closed — but the crash reproduces exactly through the OBJECTSTACK_CLIENT_DIST sibling hook the card named, which #5995 did not touch (it is a plain Vite string alias with no dependency validation). Reproduction: copy the installed @objectstack/client dist out of the workspace and point the hook at it; its three bare imports become unresolvable and the plugin dies on `ENOENT ... dist/@objectstack/core/logger`. Shape 1 implemented as ruled: the fs.readFileSync in emitEagerClosureReport is guarded by an existence check that this.error()s naming the missing file, the chunk that imports it, and the unresolved-bare-import diagnosis; the failure stays a build failure, only the message changed. Shape 2 investigated, nothing configured: the card's stated mechanism is FALSIFIED — rolldown does not emit a chunk entry for the specifier (inBundleMap false; 515 bundle keys = 508 chunks + 7 assets, none of them the specifier). It appears only inside assets/framework-*.js's `imports` array, because rolldown lists a chunk's EXTERNAL imports there beside real chunk file names, and the walk does eager.add() without asking chunks.has(). No advancedChunks.groups test matches these ids (no /node_modules/ and no /@objectstack+ segment), so the group-test suspicion is NOT supported. IMPORTANT CAVEAT for the PM: under the real console plugin stack neither the old ENOENT nor the new message reaches the developer — viteIneffectiveDynamicImports()'s closeBundle counter-probe replaces the reported build error whenever the build died earlier, so all the user sees is `43 pinned ineffective dynamic import(s) did NOT fire`. That masking is pre-existing, is a different plugin (#5325's ledger), and is filed as #6093; this fix is correct and demonstrated but stays invisible to developers until that lands.", "tests": "All on committed head b3e278420, worktree /home/user/objectui-5996, heavy commands serialized through scripts/pm/os-verify-lock.sh. PAIRED BEFORE/AFTER on one reproduction, configs differing by exactly this PR's diff (each generated by one identical sed removing viteIneffectiveDynamicImports(), which otherwise masks both errors). BEFORE: 'Build failed with 1 error: [plugin emit-eager-closure-report] Error: ENOENT: no such file or directory, open .../apps/console/dist/@objectstack/core/logger'. AFTER: 'Build failed with 1 error: [plugin emit-eager-closure-report] RolldownError: [emit-eager-closure-report] eager-closure member `@objectstack/core/logger` has no file in `.../apps/console/dist`, so its bytes cannot be weighed. It is almost certainly an UNRESOLVED BARE IMPORT ... (imported by: assets/framework-Bwcc-tp7.js) ...'. ARTIFACT FRESHNESS: vite compiles the config into a .vite-temp file whose name carries a fresh timestamp per invocation — the two runs name different files in their stack traces (before.ts.timestamp-1787588787207-3d31ebadb622b.mjs vs after.ts.timestamp-1787588797390-b421aa65b219f.mjs), and the after text exists nowhere in the repo but this diff. NON-VACUITY: real vite.config.ts, no override, on b3e278420 exits 0 with '[plugin emit-eager-closure-report] eager closure: 52/508 chunks, 3298242 bytes gzipped (3220.9 KB)' — byte-identical to the pre-fix baseline on 7c96c9420, and grep -c of the guard text in that log is 0; measured reason it is silent: the only externals on a normal build are module/fs/path from @objectstack/lint, which sits behind the lazy boundary. GATES: `pnpm --filter @object-ui/console type-check` exit 0 (tsc --noEmit then tsc -b tsconfig.node.json --force; tsconfig.node.json is the project containing vite.config.ts) — the first run's 352 TS2882 errors were the unbuilt-dependency-closure trap, none naming vite.config.ts, and green after `pnpm --workspace-concurrency=2 --filter '@object-ui/console^...' build`. `pnpm --filter @object-ui/console lint` exit 0 ('203 problems (0 errors, 203 warnings)', all pre-existing). Root vitest on the 6 config-adjacent suites: 'Test Files 6 passed (6)' / 'Tests 157 passed (157)'. `node scripts/check-changeset-presence.mjs`: 'No source of a released package changed in this range, so no changeset is owed.' DECLARED NARROWING: the repo-wide suite and the full lint farm were not run locally; CI owns that verdict. All exit codes captured before any pipe (redirect-then-capture), and every verdict above is the gate's own printed line.", "open_questions": [], "out_of_scope_findings": [ "filed as #6093: ineffective-dynamic-import-ledger's closeBundle counter-probe replaces the real error whenever an earlier console plugin fails the build — measured, the ENOENT/guard message appears 0 times under the real plugin list", "filed as #6094: OBJECTSTACK_CLIENT_DIST has no dependency-resolution validation (the asymmetry with OBJECTSTACK_SPEC_DIST after #5995), so an out-of-tree client override silently produces a bundle with bare specifiers no browser can load" ] }Generated by Claude Code