Incremental type checking vs. --noCheck for low-traffic CI builds
0 reputation · 24 Jan 2024, 17:18 UTC
0 reputation · 24 Jan 2024, 17:18 UTC
For a low‑traffic TypeScript service, the goal is to reduce CI minutes per deploy while keeping type safety guarantees.
The concrete constraint is that few deploys mean a full type‑check on every push can dominate CI cost, but skipping checks risks shipping broken types.
Approach A uses --incremental with a persisted --tsBuildInfoFile to reuse checker state; Approach B uses --noCheck to emit JavaScript without any type analysis.
What are the reliability trade‑offs of relying on cached incremental state in ephemeral CI runners versus delegating safety to a pre‑merge type‑check gate? How does cache invalidation when tsconfig or compiler options change affect correctness? Which option yields lower expected CI cost given infrequent releases?
35275 reputation · 24 Jan 2024, 18:14 UTC
For a low-traffic TypeScript service, the lower-cost and more reliable setup is a required pre-merge tsc --noEmit gate plus either a full type-check or --noCheck on the exact deploy SHA. Restoring a .tsbuildinfo cache in ephemeral CI adds operational complexity with little expected benefit when releases are infrequent.
TypeScript's incremental checker is semantically safe. A .tsbuildinfo file records file-content hashes, compiler options, the tsconfig signature, and the tsc version. When tsconfig or options change, the affected state is invalidated and rechecked. The confirmed failure mode is a slower full recheck, not an unsound "clean" result.
The likely reliability problems in ephemeral runners are operational: cache eviction after periods of inactivity, partial or corrupt restores, path/platform mismatches between the saving and restoring job, and tsc-version drift. In each case tsc fails safe by rechecking, so you lose time and cache-hit rate, not type safety.
--noCheck (TypeScript 5.6+) skips semantic analysis but still parses, resolves modules, and emits JavaScript. It is faster than a full check yet not free, and it provides zero type safety by itself. All guarantees must come from a gate that runs on the same commit being deployed.
The weak point is gate coverage. Post-merge changes—dependency bumps, merge commits, or rebases after the PR gate—can introduce type errors that --noCheck never surfaces. Mitigate by running the gate on the deploy pipeline's HEAD SHA or requiring it on every push to the deploy branch.
With infrequent releases, incremental savings are roughly hit-rate × check-time × fraction rechecked. Warm caches can save 30–70% of check time, but after eviction the benefit approaches zero while cache save/restore overhead remains. --noCheck-plus-gate shifts cost to per-PR checks the repository already runs, which usually wins on total minutes.
tsc --noEmit --extendedDiagnostics on a clean runner to measure baseline CheckTime.tsc --noCheck emit time on the same SHA.tsc --version supports --noCheck and that it composes with your emit flags.One detail would change the recommendation: the measured clean-run check time relative to total deploy-job minutes. If checking is already a small fraction of job time, run the full check and skip both optimizations.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.