Skip to content

fix(tooling): stand the ineffective-dynamic-import counter-probe down on a build that never finished - #6113

Merged
yinlianghui-tw merged 2 commits into
mainfrom
claude/issue-6093-counter-probe-masking
Aug 24, 2026
Merged

fix(tooling): stand the ineffective-dynamic-import counter-probe down on a build that never finished#6113
yinlianghui-tw merged 2 commits into
mainfrom
claude/issue-6093-counter-probe-masking

Conversation

@yinlianghui-tw

Copy link
Copy Markdown
Collaborator

Fixes#6093

scripts/vite-ineffective-dynamic-imports.ts's closeBundle counter-probe threw whenever none of the 43 pinned INEFFECTIVE_DYNAMIC_IMPORT warnings fired — and an error thrown from closeBundlereplaces the build's own error in vite 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 (editing DEFEATED_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 real vite.build() runs), those two hooks do not cover the failure this card was filed on:

build dies inbuildEnd(error)renderErrorthis plugin's writeBundle
transformERRORnot callednot called
renderChunkno argumentcallednot called
generateBundleno argumentnot callednot called
writeBundleno argumentnot callednot called
nothing (green)no argumentnot calledCALLED

The card's reproduction is the bottom failing row — a writeBundle error from emit-eager-closure-report — where buildEnd is handed nothing and renderError never fires. So the plugin also marks its own writeBundle: "closeBundle reached, writeBundle never ran" is "this build did not finish". It is declared order: 'post', which is load-bearing — with the default order the marker would be set before a later plugin's writeBundle threw, and that plugin's failure would be masked exactly as described here (measured both ways).

build.write === false is the one build that legitimately reaches closeBundle with no writeBundle, 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_DIST override whose own dependency (@objectstack/core/logger, confirmed MODULE_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'log lines
before (a100f77d3, unmodified plugin)0167
after (1ccafd03d)1044

grep -c ENOENT is 0 in both — correctly so: PR #6095 replaced that bare ENOENT with the self-diagnosing message, and that message is what now reaches the top:

error during build:
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 `/home/user/objectui-6093/apps/console/dist`, so its bytes cannot be weighed. It is almost
certainly an UNRESOLVED BARE IMPORT, not a missing build output: ...
(imported by: assets/framework-CXWLXU9l.js)

#6095's improvement was invisible until this landed; this is it becoming visible. Above it, one line:

[plugin ineffective-dynamic-import-ledger] ineffective-dynamic-import ledger NOT checked: this
build failed before its output was written, so its sightings are not a measurement (0 unpinned,
43 did not fire). Standing down instead of reporting that drift, because an error thrown from
`closeBundle` REPLACES the build's own error and would hide the real failure in this log
(objectui#6093). ...

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 the order: 'post'writeBundle really 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:

error during build:
Error [RolldownError]: 43 pinned ineffective dynamic import(s) did NOT fire. ...

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-invocation apps/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 that writeBundle really is declared order: '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 an EXIT INT TERM trap; tree verified byte-clean afterwards) turns exactly the three stand-down tests red and leaves the two control tests and the write: false test 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=267 files, 1857 tests passed
  • pnpm run type-check:scripts (tsc -p tsconfig.scripts.json) — exit 0
  • eslint --no-inline-config on the two changed files — 0 errors, 0 warnings
  • pnpm run check:control-bytescheck-control-bytes: OK (scanned 5057 tracked text file(s))
  • node scripts/check-changeset-presence.mjsNo source of a released package changed in this range, so no changeset is owed. (the diff is scripts/ only; no published package source, so no changeset is owed rather than an empty one being needed)

Repo-wide pnpm lint was narrowed to the two changed files: eslint here is not type-aware (no projectService / parserOptions.project in 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 json output (2 files), and the full farm runs in CI regardless.

Out of scope and untouched: the eager-closure report (#5996 / #6095), OBJECTSTACK_CLIENT_DIST validation (#6094 remains open, handled elsewhere), and whether unresolved bare imports should fail the console build generally.


Generated by Claude Code

… on a build that never finished
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019b5UBNMtTzKbVtZZGvFuxe
@yinlianghui-twClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM: ACCEPT — ⚠️ Correction 6: my ruling's mechanism would not have fixed this card

I ruled shape 1 and specified how: "a flag set from a buildEnd / renderError hook, because closeBundle cannot otherwise distinguish 'zero because fixed' from 'zero because dead'."

You measured it, with a harness driving real vite.build() runs on vite 8.2.1 + rolldown 1.2.3:

hookfires for
buildEndbuild-phase (transform) failures only
renderErrorrenderChunk failures only
generateBundle / writeBundle failures⚠️neither hook is told anything

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 === false is the one build that legitimately reaches closeBundle without writeBundle, 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

stateresult
1 — fails before warnings emitbefore: UNRESOLVED BARE IMPORT0, did NOT fire1, 67 log lines · after: 1, 0, 44 lines
2 — succeeds, warnings firedprobe silent, BUILD_EXIT=0, eager-closure byte-identical to baseline
3 — succeeds, warnings did not fireprobe 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 false3 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

@yinlianghui-tw
yinlianghui-tw marked this pull request as ready for review August 24, 2026 18:16
@yinlianghui-tw
yinlianghui-tw added this pull request to the merge queueAug 24, 2026
Merged via the queue into main with commit f7f0993Aug 24, 2026
21 checks passed
@yinlianghui-tw
yinlianghui-tw deleted the claude/issue-6093-counter-probe-masking branch August 24, 2026 18:28
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ineffective-dynamic-import-ledger's closeBundle counter-probe replaces the real error whenever an earlier console plugin fails the build

2 participants

@yinlianghui-tw@claude