Skip to content

[codex] Fix TypeScript build cache flake#273

Merged
blove merged 2 commits into
mainfrom
codex/fix-tsbuildinfo-cache-flake
Jun 25, 2026
Merged

[codex] Fix TypeScript build cache flake#273
blove merged 2 commits into
mainfrom
codex/fix-tsbuildinfo-cache-flake

Conversation

@blove

@blove blove commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Summary

  • moves TypeScript incremental build-info files for emitting packages into their dist output directories
  • adds a CI guard that requires emitting package tsconfigs to keep tsBuildInfoFile under dist and confirms Turbo caches dist/**
  • runs the guard in both the CI validate job and the release ci:validate script before pnpm build

Root cause

tsc -b was writing tsconfig.tsbuildinfo next to each package tsconfig, while Turbo only cached/restored dist/**. If dist disappeared but the root-level .tsbuildinfo survived, TypeScript considered @dawn-ai/sdk up to date and emitted no declarations. @dawn-ai/core then ran after the SDK task, but packages/sdk/dist/index.d.ts was still missing, causing the intermittent TS2307 cascade.

Putting build-info files in dist makes declarations and TypeScript incremental state share the same lifecycle: clean, cache restore, and cache eviction now move them together.

Validation

  • pnpm check:build-cache
  • pnpm lint
  • rm -rf packages/*/dist packages/*/*.tsbuildinfo apps/web/.next examples/chat/web/.next && pnpm build
  • pnpm build (warm cache, 17/17 cached)
  • 3x stale-state repro: stale packages/sdk/tsconfig.tsbuildinfo, deleted SDK/core dist, turbo run build --filter=@dawn-ai/core --force --concurrency=10
  • 3x cache replay: deleted SDK/core dist, turbo run build --filter=@dawn-ai/core --concurrency=10
  • pnpm typecheck
  • node scripts/check-changesets.mjs (skipped: no user-facing changes)

@vercel

vercel Bot commented Jun 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dawnai Ready Ready Preview, Comment Jun 25, 2026 4:31am

Request Review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval: this PR received an intelligent (AI) code review. See the review comments on this PR.

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 2m 20s —— View job


PR Review: Fix TypeScript build cache flake

Tasks:

  • Gather context and understand the PR
  • Read CLAUDE.md for repo guidelines
  • Review the diff and changed files
  • Analyze the new validation script
  • Review CI configuration changes
  • Post findings

Summary

This PR correctly identifies and fixes a real source of CI flakiness. The root cause analysis is sound: tsc -b writes .tsbuildinfo next to each tsconfig, but Turbo only restores dist/**. When dist/ was evicted but the stale root-level .tsbuildinfo survived, TypeScript skipped re-emitting, causing downstream declaration-not-found errors. Moving the build-info files into dist/ makes TypeScript's incremental state share the same lifecycle as its output — a clean, correct fix.

The validation script and CI guard are a good defense-in-depth addition.


Findings

Correctness

All 13 emitting packages updated correctly. Every package with outDir: "dist" and noEmit !== true now has tsBuildInfoFile: "dist/tsconfig.tsbuildinfo". The CLI's split (tsconfig.json with noEmit: true, tsconfig.build.json with noEmit: false) is handled cleanly.

packages/sdk/tsconfig.contracts.json sets noEmit: true but still sets tsBuildInfoFile: "dist/tsconfig.contracts.tsbuildinfo". TypeScript writes the build-info file even when noEmit is true (for --incremental), and putting it in dist/ means Turbo will cache/restore it along with everything else. This is the right call.

.gitignore is fine. It already ignores dist/ (and *.tsbuildinfo), so the new locations won't be accidentally committed.


Observations on scripts/check-build-cache-config.mjs

1. The guard doesn't resolve extends.
The script reads only each file's own compilerOptions, not the inherited ones from the extends chain. A new tsconfig that inherits outDir from a base config without setting it directly would silently bypass the check. This isn't a problem today (all emitting tsconfigs set their own outDir), but it's a latent gap.

// scripts/check-build-cache-config.mjs:31
const compilerOptions = config.compilerOptions ?? {}
const outDir = compilerOptions.outDir   // won't see inherited outDir

If this becomes a concern, resolving the extends chain (or at minimum flagging tsconfigs where outDir is missing locally but the file isn't noEmit) would close the gap. Fix this →

2. Only packages/ is checked.
apps/ and examples/ are out of scope. This is likely intentional (Next.js and example apps use their own build systems), but worth documenting with a comment in the script so future contributors understand the boundary.

3. Hardcoded tsconfig.tsbuildinfo name.
The first check enforces {outDir}/tsconfig.tsbuildinfo exactly. If a package ever needed two emitting tsconfigs writing to the same outDir, they'd both be required to use the same path, causing a conflict. The tsconfig.contracts.json case avoids this today only because it has noEmit: true. Not an issue now, but worth knowing if the pattern grows.


CI integration

Placing check:build-cache before pnpm build in both the CI validate job and the ci:validate script is the right order — it fails fast on misconfiguration rather than letting a broken build cache silently succeed.


Verdict

This looks good to merge. The fix is correct, minimal, and comprehensive. The validation script is a nice long-term regression guard. The observations above are minor and none block correctness.

@blove blove marked this pull request as ready for review June 25, 2026 04:31

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval: this PR received an intelligent (AI) code review. See the review comments on this PR.

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 1m 47s —— View job


PR Review: Fix TypeScript build cache flake

Tasks:

  • Gather context and understand the PR
  • Read CLAUDE.md for repo guidelines
  • Read the diff and changed files
  • Analyze the validation script
  • Review CI configuration changes
  • Post findings

Summary

The root cause analysis is correct and the fix is sound. tsc -b writes .tsbuildinfo next to each tsconfig by default, but Turbo only caches/restores dist/**. When Turbo evicted dist/ but the root-level .tsbuildinfo survived, TypeScript skipped re-emitting declarations. Moving tsBuildInfoFile into dist/ aligns TypeScript's incremental state with Turbo's cache lifecycle — a clean fix.


Correctness

All 13 emitting tsconfigs updated correctly. Every package with outDir: "dist" and noEmit !== true now has tsBuildInfoFile: "dist/tsconfig.tsbuildinfo".

CLI split handled correctly. packages/cli/tsconfig.json has noEmit: true (no outDir, no tsBuildInfoFile required). Only tsconfig.build.json — which sets noEmit: false — needed the tsBuildInfoFile addition.

tsconfig.contracts.json handled correctly. It sets noEmit: true with tsBuildInfoFile: "dist/tsconfig.contracts.tsbuildinfo". TypeScript still writes build-info when incremental: true, and putting it in dist/ means Turbo evicts it together with SDK's declarations. The distinct filename (tsconfig.contracts.tsbuildinfo vs tsconfig.tsbuildinfo) avoids collision with the regular SDK build-info.

.gitignore is already correct — both dist/ and *.tsbuildinfo are present, so no new paths will be accidentally committed.


Script analysis (scripts/check-build-cache-config.mjs)

One correctness observation: the second check's outDir fallback (line 47).

!toPosix(tsBuildInfoFile).startsWith(`${toPosix(outDir ?? "dist")}/`)

When outDir is undefined (e.g., a noEmit: true config that inherits outDir via extends), this falls back to "dist". Today that's fine — all noEmit tsconfigs either have no tsBuildInfoFile (so the check is skipped) or explicitly put it under dist/. But if a future tsconfig has noEmit: true, inherits a non-"dist" outDir, and sets tsBuildInfoFile accordingly, this check would misfire. Low risk since the fallback matches the convention, but worth noting.

No extends resolution — already noted in the prior review. The script reads only each file's own compilerOptions, not inherited ones. A new tsconfig that inherits outDir from a base config without setting it locally would bypass the first check (line 33). Not an issue today since all emitting tsconfigs set their own outDir, but a latent gap.

No error handling on JSON parsing. readJson will throw a bare Node.js stack trace on malformed JSON. Minor — this would only affect contributors who accidentally break a tsconfig file.

Turbo output check is correct. dist/** is present in turbo.json's build.outputs, so this check passes today and will catch any future removal.


CI changes

Placing check:build-cache before pnpm build in both the CI validate job and ci:validate script is the right order — it fails fast on misconfiguration rather than letting a broken cache state silently pass.


Verdict

Looks good to merge. The fix is correct, minimal, and comprehensive across all 13 emitting packages. The validation script is a solid regression guard. The script observations above are minor and none affect correctness today.

@blove blove enabled auto-merge (squash) June 25, 2026 04:31
@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 1m 37s —— View job


PR Review: Fix TypeScript build cache flake

Tasks:

  • Gather context and understand the PR
  • Read CLAUDE.md for repo guidelines
  • Review the diff and changed files
  • Analyze the validation script
  • Post findings

Summary

The fix is correct and well-scoped. The root cause (Turbo evicting dist/ but leaving a stale root-level .tsbuildinfo that fooled tsc into skipping re-emission) is accurately diagnosed, and moving tsBuildInfoFile into dist/ is the right remedy. Declarations and incremental state now share the same Turbo cache lifecycle.

Verified:

  • turbo.json already has dist/** in build outputs — no change needed there
  • All 14 emitting tsconfigs set outDir: "dist" locally (not via inheritance), so the validation script's extends-resolution gap (see below) has no current impact
  • .gitignore covers both *.tsbuildinfo and dist/ — no risk of accidental commits
  • CI guard is placed before pnpm build in both the validate job and ci:validate script — fails fast on misconfiguration

Findings

scripts/check-build-cache-config.mjs — one latent gap

The script reads compilerOptions.outDir directly from each file without resolving the extends chain (scripts/check-build-cache-config.mjs:31). A future tsconfig that inherits outDir without declaring it locally would pass the check silently even if it lacks a tsBuildInfoFile.

// Line 31 — outDir from the extends chain is invisible here
const outDir = compilerOptions.outDir

This isn't a current issue — every package sets outDir locally — but the fallback outDir ?? "dist" in the second check (line 49) papers over the gap for tsconfig.contracts.json (which inherits outDir: "dist" from tsconfig.json). Worth a comment in the script to make the assumption explicit, or a light extends-resolution step to close it properly. Fix this →

Other observations (non-blocking)

  • Scope: Only packages/ is validated. apps/ and examples/ are intentionally excluded (Next.js / example apps use their own build systems). A one-line comment in the script explaining this boundary would help future contributors.
  • tsconfig.contracts.json: noEmit: true with tsBuildInfoFile: "dist/tsconfig.contracts.tsbuildinfo" is correct — TypeScript still writes incremental state for --incremental even when not emitting, and placing it in dist/ means it travels with the cache.

Verdict

Good to merge. The fix is minimal, correct, and comprehensive. The validation script is a solid regression guard. The extends-resolution gap is latent, not current, and doesn't block this change.

@blove blove merged commit c981130 into main Jun 25, 2026
7 checks passed
@blove blove deleted the codex/fix-tsbuildinfo-cache-flake branch June 25, 2026 04:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant