Skip to content

fix(ci): stabilize TS transpile cache and pin TypeScript to 6.x#140

Merged
jsugg merged 1 commit into
mainfrom
fix/ts-transpile-cache-and-pin
Jul 14, 2026
Merged

fix(ci): stabilize TS transpile cache and pin TypeScript to 6.x#140
jsugg merged 1 commit into
mainfrom
fix/ts-transpile-cache-and-pin

Conversation

@jsugg

@jsugg jsugg commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Why

main unit tests started failing after the weekly dependency bumps merged (#132/#135/#137), even on unrelated PRs (e.g. a README-only change). Two independent latent bugs in the test transpile-cache helper were exposed:

  • Non-atomic cache publish. tests/helpers/registerTsTranspile.cjs transpiles .ts files into a cache shared by concurrently spawned script children. It wrote entries with a plain fs.writeFileSync, so under load one child could read another's half-written entry — a truncated module drops its trailing exports and fails at runtime with ... is not a function. The dependency bump shifted process-startup timing enough to lose this race reliably (old deps: 5/5 pass; new deps: 5/5 fail).
  • Truncated cache key. The key was base64url(tsVersion \0 absPath \0 mtime).slice(0, 180). On long checkout paths the truncation cuts before the filename, so sibling modules collide to one cache entry and a module is served another file's transpile.

Symptom: workflowScriptsBoundary.spec.ts › observability backend validator writes JSON diagnostics on process success and failure(0, backendSnapshot_1.parseObservabilitySnapshotCliOptions) is not a function.

What

  • Atomic publish: write to a unique temp file, then rename (atomic on the same filesystem) so a reader only ever sees a complete entry or none.
  • Hash the cache key (sha256, fixed-length, collision-resistant) instead of truncating a path string.
  • Pin TypeScript to 6.x in Dependabot: typescript-eslint@8 peer-requires typescript <6.1.0, so a semver-major bump (the open TS-7 PR) fails npm ci with ERESOLVE. Ignore semver-major typescript updates until the type-aware lint toolchain supports the next major (mirrors the existing eslint / @eslint/js pins). Contract updated to enforce it.

Verification

  • The previously-failing spec: 0/5 → 5/5 on the merged deps.
  • Full unit + contract suites green (530 tests), plus lint / typecheck / format:check / workflows:lint.

No product/source behavior changes — test-tooling and dependency policy only.

registerTsTranspile.cjs shared a transpile cache across concurrently spawned
script children but published entries with a non-atomic writeFileSync and keyed
them by a path string truncated to 180 chars. Under load a child could read a
half-written entry, and long checkout paths collided sibling modules to one
entry, both yielding a truncated module missing its trailing exports ('X is not
a function' in workflowScriptsBoundary.spec.ts). Publish atomically via a unique
temp file + rename, and key the cache by a sha256 hash so it is fixed-length and
collision-resistant.

Also ignore semver-major typescript bumps in Dependabot: typescript-eslint@8
peer-requires typescript <6.1.0, so a 7.x bump fails npm ci with ERESOLVE.
@jsugg jsugg merged commit 1ebe0b5 into main Jul 14, 2026
24 checks passed
@jsugg jsugg deleted the fix/ts-transpile-cache-and-pin branch July 14, 2026 17:47
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