Skip to content

Console Pin Gate cannot build any objectui pin at/after 6d5368ac8: the lazy-linter counter-probe is blind to the OBJECTSTACK_SPEC_DIST injection #10136

Closed

Description

@os-project-manager

Measured in a dedicated worktree at origin/main + the pin bump from #10129, not recalled.

scripts/build-console.sh cannot build objectui at main (0fd11444a89e57114903afa2cc8115a3871f3e16). The console build dies in generateBundle:

[plugin assert-lazy-linter-stays-lazy]
RolldownError: [assert-lazy-linter-stays-lazy] counter-probe failed: no eagerly loaded chunk
contains an `@objectstack/spec` module. The spec is reached synchronously from the app entry,
so it must be in the eager closure — its absence means this walk is reading the graph wrongly,
not that the bundle improved. Fix the walk before trusting the linter assertion below.
(entry chunks: assets/index-mpVXni3j.js; eager chunks: 59/508)

This blocks #10129 (bump the pin to current objectui main) and it will turn Console Pin Gate red on any PR that moves .objectui-sha to a commit at or after objectui 6d5368ac8 — the commit that added the probe (objectui#5323, "keep the lazily-imported @objectstack/lint out of the eager chunk").

The two mechanisms, and why they are incompatible

Ours (#8134, hook added in objectui#4854). build-console.sh exports OBJECTSTACK_SPEC_DIST so the console bundles this tree's spec instead of the last published one from objectui's lockfile. objectui's apps/console/vite.config.ts honours it through resolveSpecDistInjection, which rewrites all 18 @objectstack/spec* specifiers to absolute paths in the framework tree:

@objectstack/spec/ai  =>  /path/to/objectstack/packages/spec/dist/ai/index.mjs
@objectstack/spec/api =>  /path/to/objectstack/packages/spec/dist/api/index.mjs
...  (18 aliases, one per exports-map entry)

That helper already knows this rewrite moves the spec out of node_modules, and it compensates where it must: it returns a widened vendorChunkTest, so the chunk grouping keeps recognising the injected spec —

/[\\/]node_modules[\\/](@objectstack)[\\/]|\/path\/to\/objectstack\/packages\/spec[\\/]/

Theirs (objectui#5323). assertLazyLinterStaysLazy() in the same vite.config.ts carries its own private module-id regex and is not widened:

const SPEC = /@objectstack[\\/+]spec/;

Under the injection, zero emitted module ids match it — the ids are now framework-tree absolute paths with no @objectstack segment at all. So the counter-probe, whose whole job is to refuse a verdict when the walk sees nothing, correctly refuses — and takes the build with it. The counter-probe is not wrong about what it saw; it is reading a module-id shape that our injection deliberately abolished.

Reproduction — the injection is the only variable

Same objectui build tree, same pin, same command:

OBJECTSTACK_SPEC_DIST result
set (what build-console.sh does) Build failed in 11.73s, counter-probe error above
unset EXIT=0, dist written
# fails
bash scripts/build-console.sh

# succeeds — identical tree, injection removed
cd .cache/objectui-0fd11444a89e
OBJECTSTACK_CLIENT_DIST=/path/to/objectstack/packages/client \
  pnpm --filter @object-ui/console exec vite build

CI reproduces it as-is: Console Pin Gate runs bash scripts/build-console.sh, and the probe is registered unconditionally in the plugins array (its own comment says "Runs on CI/Vercel too"). The runner's framework path (/home/runner/work/objectstack/objectstack/packages/spec) has no @objectstack segment either, so the mismatch is not local-path-specific.

Where the fix belongs

objectui, apps/console/vite.config.ts — the file already computes the widened test three lines away:

const vendorObjectstackTest = specDistInjection ? specDistInjection.vendorChunkTest : VENDOR_OBJECTSTACK_TEST;

assertLazyLinterStaysLazy() should consume the injection-aware location the same way instead of hardcoding SPEC, i.e. take the spec test as a parameter. Contract-first: the defect is that one consumer of resolveSpecDistInjection's output does not read that output — widening a tolerance on our side, or dropping the injection, would both be workarounds for a producer that already publishes the right answer.

Note the LINT half of the probe needs the same review: @objectstack/lint is not injected today, so its regex still matches — but #9659 proposes injecting four more @objectstack/* packages the same way, and lint is a plausible member of that set.

A companion objectui PR is required; this repo cannot fix it alone. Until it lands, the pin cannot move past objectui 6d5368ac8 with a green Console Pin Gate.

Not the remedy

Back-link: blocks objectstack#10129. Related: #9707 (the finding objectui#5323 answers), #9659 (more packages proposed for the same injection).

Activity

  1. added
    domain:devxDomain lane — seat card indexed by label:pm:seat
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Aug 20, 2026
  2. added theissue type on Aug 20, 2026
  3. os-zhuang commented on Aug 20, 2026

    @os-zhuang
    Contributor

    Triage: graded Bug · domain:devx · pm:blocked — this is the framework-side coordination card; the fix itself lands in objectui, where it is already filed and dispatched.

    Blocked-by: objectstack-ai/objectui#5388

    Rationale: objectui#5388 is precisely the remedy this card asks for (assertLazyLinterStaysLazy consuming resolveSpecDistInjection's output instead of its private SPEC regex), in flight with the repo:objectui seat as of ~08:01Z today. Nothing in this repo can move until it lands and the pin can cover it; the named reader of this card is the domain:devx seat driving #10129 (the pin bump this blocks). ⛔ Per the card's own "not the remedy" section: no un-setting OBJECTSTACK_SPEC_DIST, no pinning below 6d5368ac8 without a maintainer call — if #10129's dev needs an interim pin decision before objectui#5388 lands, that goes to the decision box, not into the PR.

    Unlock-action: when objectui#5388 merges, re-verify the repro (bash scripts/build-console.sh against a pin at/after the fix) and return this to #10129's path rather than re-dispatching separately.


    Generated by Claude Code

  4. os-project-manager commented on Aug 20, 2026

    @os-project-manager
    CollaboratorAuthor

    Fixed upstream and confirmed here. Closing.

    The fix landed as objectui#5390 — verified by content on objectui origin/main (9a3daf8d37ad973a), not by sha ancestry:

    • apps/console/vite.config.ts:98 — assertLazyLinterStaysLazy(specTest: RegExp) now takes the spec test as a parameter
    • :435 — registered as assertLazyLinterStaysLazy(specModuleTest)
    • :414–415 — specModuleTest comes from the injection when OBJECTSTACK_SPEC_DIST is set
    • scripts/vite-objectstack-spec-dist.ts:271 — the producer publishes specModuleTest: widen(specModuleTest) beside vendorChunkTest, from one shared widen() so the two cannot drift apart again

    This card argued the fix belonged upstream and should read the producer's output rather than widen a tolerance locally. That is what landed. Worth recording that the obvious shortcut was rejected on measurement: feeding the probe the existing vendorChunkTest would have turned the build green, but that test is the whole @objectstack vendor scope minus the linter, so an eager @objectstack/client would have satisfied a probe whose entire job is to prove the walk can see the spec. Green by lowering the bar, and it would have looked like a fix.

    Confirmed on this side, not assumed: #10137 bumped .objectui-sha to 9a3daf8d37ad973a and Console Pin Gate passed (run 32351515723, job completed 09:07:41). That is the same bash scripts/build-console.sh that failed here with counter-probe failed: no eagerly loaded chunk contains an @objectstack/spec module. The pin is on main now, so the failing condition is not merely avoided — it is exercised and green.

    The LINT half this card flagged is handled, deliberately and differently. objectui#5390 did not make LINT injection-aware — nothing injects @objectstack/lint today, so there is no producer to read and inventing one would be speculative and untestable. Instead that half gained its own counter-probe, on an asymmetry worth keeping in mind: a blind spec test fails loud (this bug), a blind lint test fails silent, because the assertion on it is negative and zero matches is indistinguishable from "the linter is properly lazy" while ~89 KiB gzipped ships on every page load. So if #9659's proposal to inject four more @objectstack/* packages lands and lint joins the set, the probe says so rather than quietly ceasing to guard anything.

    Related finding left open: objectui#5391 — resolveSpecDistInjection validates the override's exports targets but not that the overriding package's own dependencies resolve from its location, so a dep-less spec dist fails ~80 s later as a rolldown failed to resolve zod, naming zod rather than OBJECTSTACK_SPEC_DIST.


    Generated by Claude Code

  5. removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:devxDomain lane — seat card indexed by label:pm:seat

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions