Skip to content

fix(build): build @objectstack/client through turbo in build-console.sh, and guard on the declaration - #9396

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-9307-cold-tree-client-build
Aug 17, 2026
Merged

fix(build): build @objectstack/client through turbo in build-console.sh, and guard on the declaration#9396
os-steve merged 1 commit into
mainfrom
claude/issue-9307-cold-tree-client-build

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

Fixes#9307

scripts/build-console.sh pre-built @objectstack/client by shelling into the package
(cd "$CLIENT_PKG" && pnpm build), which bypasses the ordering turbo.json already
declares ("build": { "dependsOn": ["^build"] }). On a cold tree packages/spec/dist
and packages/core/dist do not exist, the client's subpath imports resolve to nothing,
and the tsup DTS pass dies.

This is latent, not a fresh regression.git log -L 136,141:scripts/build-console.sh
returns exactly one commit, b990bc258 (2026-07-02, #2513) — the block has been unchanged
for 3549 commits. It only bites on a genuinely cold tree, which is why it stayed invisible.

What changed

Two edits, both inside the client pre-build block:

  1. Ordering — the client is now built through turbo from the framework root, so ^build
    supplies the dependency closure:
    (cd "$FRAMEWORK_ROOT" && pnpm exec turbo run build --filter=@objectstack/client)
  2. Staleness guard — keyed on dist/index.d.ts instead of dist/index.mjs. tsup writes
    the CJS/ESM bundles before the DTS pass, so a client build that died in DTS still left
    dist/index.mjs on disk and the next run skipped the build entirely.

The run is also pinned to $FRAMEWORK_ROOT; the old code cd'd into the package, so the new
command must not inherit whatever cwd the caller had.

Measured, not assumed

The card was explicit that its failure mode came from static reading plus a one-commit
git log -L, not an executed bisect. It was reproduced for real here, on a fresh
git worktree + pnpm install with zero dist/ directories anywhere under packages/.

Red, before. Cold tree, unmodified script — exit 1 in 6.4s, the same nine diagnostics
the card reports:

DTS Build start
src/index.ts(3,91): error TS2307: Cannot find module '@objectstack/spec/data' or its corresponding type declarations.
src/index.ts(88,38): error TS2307: Cannot find module '@objectstack/core/logger' or its corresponding type declarations.
Error: error occurred in dts build

packages/client/dist was left holding index.js, index.mjs and their maps — and zero
declaration files.

Green, after. Same tree returned to cold (all dist/ removed, .turbo removed),
patched script — exit 0 in 1m32s, full console SPA built and copied:

→ @objectstack/client dist absent or incomplete (no dist/index.d.ts) — building it and its deps first...
Tasks: 32 successful, 32 total
→ Console will bundle @objectstack/client from .../packages/client
✓ Bundle canary 'import/jobs' present — framework client is in the bundle.
✓ @objectstack/console dist ready (52308 KB) from objectui@665661ab0932

Second-order leg — the half that makes this deterministic rather than flaky. A failed
client build was reproduced exactly as the old code invoked it, leaving index.mjs with no
declarations. Both guards evaluated against that state:

OLD guard [[ ! -f dist/index.mjs ]] = FALSE -> SKIPS (silent pass)
NEW guard [[ ! -f dist/index.d.ts ]] = TRUE -> REBUILDS (fixed)

Running the patched script against that half-built dist rebuilt the client and finished
green, with index.d.ts and index.d.mts present afterwards. The old script, run twice on
the cold tree, genuinely skipped the client stage on its second invocation — the
"dist missing" line is absent from that log.

One correction to the card

The card predicts the second invocation "appears to succeed". On a purely cold tree it
does not — it fails much later and more confusingly, 74s into the console SPA build:

Error: [vite]: Rolldown failed to resolve import "@objectstack/spec/data" from ".../packages/client/dist/index.js".

The silent pass needs packages/spec/dist and packages/core/dist to have been populated
by something else in between (a concurrent pnpm build or typecheck — the card's own Run 2
notes exactly that interleaving). So the guard defect is real and worth fixing on both
counts: in one state it hides a declaration-less client behind a green run, and in the other
it converts a 6s, precisely-worded failure into a 74s one that points at the client dist
rather than at the missing dependency dists. Neither is affected by which state you land in
after this change.

Filter form

The card's workaround used --filter=@objectstack/client... and its suggested fix used
--filter=@objectstack/client; those are different filters, so this was measured. The bare
form is sufficient — --filter=@objectstack/client puts 1 package in scope and turbo still
runs 32 build tasks, because the closure comes from dependsOn: ["^build"], not from the
filter suffix. packages/spec/dist (107 entries) and packages/core/dist (12 entries) are
both populated by it.

Declaration artifact

tsup 8.5.1 emits bothdist/index.d.mts and dist/index.d.ts here, in the same DTS
pass. dist/index.d.ts is the sentinel used, because it is the file this package declares
as its type entrypoint in package.json ("types" and exports["."].types) — its absence
is the actual defect, not an incidental artifact. Under OS_SKIP_DTS there is legitimately
no declaration, so the guard re-fires every run; that is harmless, since the turbo build it
guards is cached.

Scope

scripts/build-console.sh only. No turbo.json change, and no sweep of other pnpm build
shell-outs. Two observations recorded rather than acted on: the script's header already
states a "no if-exists-skip shortcut" principle that this block predated, and the shared
turbo worktree cache noted below.

Verification

node scripts/pm/dispatch-gates.mjs scripts/build-console.sh matches 0 check families
(35 undetermined, 69 silent). That is a silent verdict and not a clearance, which is why
the evidence above is executed cold-tree runs rather than gates. bash -n passes and the
control-character scan is clean.

Note on method: turbo 2.10 reports "Remote caching disabled, using shared worktree cache",
and cache hits in this container were replayed from a sibling agent's worktree. That makes
the turbo leg of the green run a cache restore rather than a from-scratch compile. It does
not affect the verdict — the dependency dist/ trees and the client declarations were
confirmed present on disk either way — but a fresh CI runner will do the real compile.

No changeset: this is a root build script, nothing published changes.

Generated by Claude Code


Generated by Claude Code

…sh, and guard on the declaration
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja
@os-steveos-steve added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 17, 2026 — with Claude
@os-steve
os-steve marked this pull request as ready for review August 17, 2026 22:56
@os-steve
os-steve added this pull request to the merge queueAug 17, 2026
Merged via the queue into main with commit 23d991bAug 17, 2026
24 checks passed
@os-steve
os-steve deleted the claude/issue-9307-cold-tree-client-build branch August 17, 2026 23:09
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-steve@claude