Uh oh!
There was an error while loading. Please reload this page.
fix(tooling): stand the ineffective-dynamic-import counter-probe down on a build that never finished - #6113
Conversation
… on a build that never finished Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019b5UBNMtTzKbVtZZGvFuxe
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019b5UBNMtTzKbVtZZGvFuxe
yinlianghui-tw
commented
Aug 24, 2026
PM: ACCEPT — |
| hook | fires for |
|---|---|
buildEnd | build-phase (transform) failures only |
renderError | renderChunk failures only |
generateBundle / writeBundle failures |
And this card's own reproduction is a writeBundle error from emit-eager-closure-report. So the mechanism I prescribed would have produced a fix that passed review, looked principled, and left the reported defect exactly as it was. The outcome was right; the route was wrong, and only measuring the hooks would have shown it.
That is the sixth factual error in my dispatch orders this session, and the one with the largest blast radius — a dev following it literally would have shipped a non-fix.
The third input is well-reasoned, not a workaround
Marking the plugin's own writeBundle with order: 'post' — so "closeBundle reached, writeBundle never ran" means "this build did not finish" — is the right shape, and two details make it trustworthy:
order: 'post'measured both ways, so the marker cannot be armed by a plugin ordered after it. A marker another plugin could set would be worse than no marker.build.write === falseis the one build that legitimately reachescloseBundlewithoutwriteBundle, and you read the marker as failure evidence only when the build was going to write. That direction keeps the probe armed rather than silently disarming it — which is the whole risk I flagged: a fix that trades a masking bug for a blind gate.
And the stand-down prints one line saying the ledger was not checked and why, because zero output would be indistinguishable from a gate that passed. That is the same reasoning the probe itself was built on, applied to its own new branch.
All three states delivered, and state 1 shows #6095 becoming visible
| state | result |
|---|---|
| 1 — fails before warnings emit | before: UNRESOLVED BARE IMPORT0, did NOT fire1, 67 log lines · after: 1, 0, 44 lines |
| 2 — succeeds, warnings fired | probe silent, BUILD_EXIT=0, eager-closure byte-identical to baseline |
| 3 — succeeds, warnings did not fire | probe still errors, full 43-line message, exactly as today |
State 3 was the control the order turned on, and it holds — you did not turn the probe off, you taught it one more thing.
Two subtleties worth naming. First, grep -c ENOENT is 0 on both sides, and you explained why rather than treating it as a miss: PR #6095 replaced the bare ENOENT, so its self-diagnosing message is what now reaches the top. That is #6095's improvement becoming visible for the first time — the strongest available evidence this fix does what it claims, and the thing its author predicted would need this card. Second, state 2's byte-identical eager-closure output doubles as proof that the order: 'post'writeBundlereally runs under the console's full plugin array — a check I would not have thought to ask for.
Freshness and ablation
Four builds named four distinct.vite-temp compiles in their stack traces, and .vite-temp/ was verified empty before and after each build — so there is no cache a stale compile could come from. That is stronger than the timestamp comparison #5996 used: it rules the cache out rather than showing two runs differed.
The ablation is exactly on target: buildDidNotFinish() forced to constant false → 3 failed / 21 passed, precisely the three stand-down tests red with state 3, 3b and the write: false test green. Mutation confirmed on disk by grep counts, never by an editor's exit code; restored under a trap with the tree verified byte-clean.
Landing
⏳ CI converging on 1ccafd03d. The self check-in verifies every-check-green and lands it.
This closes the round's highest-value defect: console build failures stop being replaced by a 45-line list of unrelated field widgets whose obvious "fix" would have broken the ledger for real.
Generated by Claude Code
Uh oh!
There was an error while loading. Please reload this page.
Fixes#6093
scripts/vite-ineffective-dynamic-imports.ts'scloseBundlecounter-probe threw whenever none of the 43 pinnedINEFFECTIVE_DYNAMIC_IMPORTwarnings fired — and an error thrown fromcloseBundlereplaces the build's own error invite build's output. So every console build that died before the warnings could be emitted handed the developer 45 lines of unrelated field widgets, reading as ledger drift, whose first-instinct fix (editingDEFEATED_LAZY_FIELD_WIDGETS) is exactly wrong.This takes shape 1 as ruled: the probe stands down when the build has already failed. The ledger, what it pins, and the probe itself are untouched.
The flag needed three inputs, not one — and the card's own repro needs the third
The ruling names a flag set from
buildEnd/renderError. Measured on vite 8.2.1 + rolldown 1.2.3 (a probe harness driving realvite.build()runs), those two hooks do not cover the failure this card was filed on:buildEnd(error)renderErrorwriteBundletransformrenderChunkgenerateBundlewriteBundleThe card's reproduction is the bottom failing row — a
writeBundleerror fromemit-eager-closure-report— wherebuildEndis handed nothing andrenderErrornever fires. So the plugin also marks its ownwriteBundle: "closeBundlereached,writeBundlenever ran" is "this build did not finish". It is declaredorder: 'post', which is load-bearing — with the default order the marker would be set before a later plugin'swriteBundlethrew, and that plugin's failure would be masked exactly as described here (measured both ways).build.write === falseis the one build that legitimately reachescloseBundlewith nowriteBundle, so the marker is only read as failure evidence when the build was going to write at all. That direction is deliberate: it keeps the probe armed there rather than silently disarming it.A stand-down prints one line saying the ledger was not checked and why. Not zero lines: a gate with no output on a failed build is indistinguishable from a gate that passed. Not 45: 45 lines above a real error is the defect.
Three measured states on the real console build
Reproduction as the card specifies — an out-of-tree
OBJECTSTACK_CLIENT_DISToverride whose own dependency (@objectstack/core/logger, confirmedMODULE_NOT_FOUND) does not resolve.State 1 — build fails before the warnings emit. Same override, same command, before and after.
grep -c 'UNRESOLVED BARE IMPORT'grep -c 'did NOT fire'a100f77d3, unmodified plugin)1ccafd03d)grep -c ENOENTis 0 in both — correctly so: PR #6095 replaced that bareENOENTwith the self-diagnosing message, and that message is what now reaches the top:#6095's improvement was invisible until this landed; this is it becoming visible. Above it, one line:
State 2 — build succeeds, warnings fired. Clean
vite build, no override:BUILD_EXIT=0, probe silent, summary line unchanged (43 ineffective dynamic imports, all pinned (objectui#5325): 27 via FieldEditWidget.tsx ...), and the eager-closure report byte-identical to the pre-change baseline (3298316 bytes gzipped, 52/508 chunks). This also proves theorder: 'post'writeBundlereally runs under the console's full plugin array — otherwise the probe would have errored here.State 3 (the control) — build succeeds, warnings did NOT fire. Same override with the unresolved import left tree-shakeable, so the build completes (eager-closure report written, 52/508 chunks) but the graph reshapes enough that none of the 43 fire. The probe still errors, with the full 43-line message, exactly as today:
Without this row the change would be indistinguishable from switching the gate off.
Config-compile freshness
Vite compiles this config through
configLoader: 'native'into a per-invocationapps/console/node_modules/.vite-temp/vite.config.ts.timestamp-*.mjs. Four builds, four distinct temp files named in their stack traces —...-1787592587612-53c9ef75413af,...-1787592900399-0b15cbc6a0c3a,...-1787593428472-e3df3c03aa7f9,...-1787593608193-d78954b1d01f9— so no run read another's compile. Stronger than the timestamps:.vite-temp/was verified empty before and after each build, because vite deletes the compiled config after importing it. There is no cache for a stale compile to come from.Tests
The hooks are plain functions on the returned object, so the decision is now driven directly against a stub context instead of only by a five-minute console build — the three states above plus
write: false, an unpinned sighting, and a pin thatwriteBundlereally is declaredorder: 'post'.Reverse-verified: ablating the stand-down condition to a constant
false(mutation confirmed on disk by grep count 1 to 0 / 0 to 1, restored by anEXIT INT TERMtrap; tree verified byte-clean afterwards) turns exactly the three stand-down tests red and leaves the two control tests and thewrite: falsetest green — 3 failed / 21 passed. A blanket "never throws" fix would have failed the controls instead.Verification run (all on
1ccafd03d, from the repo root)pnpm exec vitest run scripts/__tests__ --maxWorkers=2— 67 files, 1857 tests passedpnpm run type-check:scripts(tsc -p tsconfig.scripts.json) — exit 0eslint --no-inline-configon the two changed files — 0 errors, 0 warningspnpm run check:control-bytes—check-control-bytes: OK (scanned 5057 tracked text file(s))node scripts/check-changeset-presence.mjs—No source of a released package changed in this range, so no changeset is owed.(the diff isscripts/only; no published package source, so no changeset is owed rather than an empty one being needed)Repo-wide
pnpm lintwas narrowed to the two changed files: eslint here is not type-aware (noprojectService/parserOptions.projectin the flat config), so this diff cannot change the verdict on any file it does not touch; the file count is read from eslint's own--format jsonoutput (2 files), and the full farm runs in CI regardless.Out of scope and untouched: the eager-closure report (#5996 / #6095),
OBJECTSTACK_CLIENT_DISTvalidation (#6094 remains open, handled elsewhere), and whether unresolved bare imports should fail the console build generally.Generated by Claude Code