Bun 1.4.3 at Scrydon: Our Type-Check Gate Moves to bun check
Bun 1.4.3 shipped bun check, a TypeScript type checker ported from typescript-go, on 10 October. The same day we moved the type-check gate of Scrydon's monorepo onto it. Across 104 TypeScript projects it matched tsc byte for byte in 103. A cold check of the whole repository fell from 124.9 s to 51–68 s, peak memory fell by about a third, and an entire build phase disappeared. It does not beat tsc's incremental mode on a warm re-check, and one project stays on tsc until a bug is fixed.
On 10 October, Bun released Bun 1.4.3. Most of the release is the usual run of runtime improvements. One line in it mattered more to us than the rest: bun check, a TypeScript type checker built into Bun.
The same day, a branch moving every type-check in Scrydon's monorepo onto it was up for review. Two months ago we wrote about Bun 1.4 cutting CPU and memory in the services we run. This post covers the other end of the pipeline: the type-checker that every change has to get past before it ships.
Two runs of the whole graph; the range is the honest number.
Peak resident memory of the whole process tree.
The one mismatch stays on tsc, with an expiry date.
tsc → Bun 1.4.3 bun check.These are local measurements on the branch, not CI figures yet. We will report what the gate does in CI once it has run there. They are still enough to explain why we moved on release day.
Why bun check Mattered to Us
In our monorepo, type-checking was the job that used the most memory. Our repository has 92 TypeScript packages and apps, and 104 type-check projects. On a developer laptop, a full cold check took two minutes and peaked near 14 GB. In CI it was the reason we capped type-check concurrency at two: more than that ran a 7 GB runner out of memory.
Part of the cost was not type-checking at all. tsc reads a referenced project's emitted declaration files, not its source. So before anything could be checked, a separate phase depended on 53 package builds, emitted their declarations and cached the result between runs. Measured on its own, that phase took 67.5 s and 4.1 GB. It existed only to stop the checker from failing with "output file has not been built from source file".
bun check changes both halves. Bun describes it as a port of typescript-go, the Go rewrite of the TypeScript compiler. It reads tsconfig.json, reports the same errors as tsc and uses every core. It also follows project references from source, and it writes nothing: no JavaScript, no .d.ts, no .tsbuildinfo.
Bun's published results are strong:
- 3x to 6.4x faster than
tsc7.0.2 on a 16-core Apple silicon Mac, with 2.2x to 4.9x less memory; - all 51,210 tests of TypeScript 7.0.2's conformance suite pass on every commit;
- identical output to
tscin 1,102 of 1,103 configurations across 72 open-source projects.
We wanted to know whether the same held for ours.
What We Changed
As with Bun 1.4, we upgraded Bun as one runtime contract, not as a single version string:
- the root package-manager pin, our shared GitHub Actions set-up and every workflow override;
- every Dockerfile, on digest-pinned
oven/bun:1.4.3images; - local runtime tooling and development documentation; and
- the Bun type definitions.
@types/bun1.4.3 was not yet published, so we pinnedbun-types1.4.3 directly.
Then the gate itself changed:
- 91 of 92 packages now type-check with
bun check. - The declarations phase is gone. That includes the root task, its solution-level config, the CI step that emitted declarations and the cache that held them.
- Package builds still use
tsc.bun checkonly checks. Our published packages still need emitted.d.ts, so only the check moved.
A test in the repository fails if any package's type-check drifts back to tsc. It also fails if a type-check drops the flags described below. The one deliberate exception has to carry a guard that expires it.
What We Measured
We ran both pipelines over the same state. Every type-check task in the repository ran with Turborepo's cache forced off, packages already built.
The old pipeline was the declarations phase followed by every check. The new one is every check, run twice. We report both runs because they differed by 17 seconds. A best-of-one figure would overstate the result.
For a single heavy project we also measured tsc warm. That is its incremental mode: .tsbuildinfo already on disk, after no change and after a one-line change.
tsc → Bun 1.4.3 bun check--force; packages pre-builtWall time, whole repository, cold
secondsPeak memory, whole repository, cold
GBWall time, one heavy service
secondsPeak memory, one heavy service
GBtsc run with each of the two bun check runs. The single-project rows are one of our heaviest services; its warm, unchanged tsc re-check (0.15 s, 40 MB) is in the table below rather than the plot.| Run | Wall time | Change | Peak memory | Change |
|---|---|---|---|---|
| Whole repository, tsc, cold Declarations phase + every check | 124.9 s | — | 13.7 GB | — |
| Whole repository, bun check, two runs | 50.7 s / 67.9 s | 46–59% | 9.5 / 9.2 GB | 31–33% |
| Whole repository, tsc, warm Incremental .tsbuildinfo, for reference | 29.8 s | — | 5.4 GB | — |
| Declarations phase alone Removed entirely | 67.5 s | Gone | 4.1 GB | Gone |
| One service, cold | 9.9 → 1.57 s | 6.3x faster | 3.5 → 0.83 GB | 4.2x less |
| One service, warm, one-line change | 0.81 → 1.57 s | 1.9x slower | 0.85 → 0.83 GB | Flat |
| One service, warm, nothing changed | 0.15 → 1.57 s | 10x slower | 0.04 → 0.83 GB | Higher |
The single-project result lands inside Bun's own published range: 6.3x faster and 4.2x less memory on a cold check. The whole-repository result is smaller, roughly 2x faster and a third less memory. We have not profiled where the gap goes. A whole-graph run includes Turborepo scheduling and dozens of small packages, which a single-project benchmark does not.
The biggest structural gain does not show up in a speed ratio at all. A phase that cost 67.5 s and 4.1 GB is now gone, and so is its cache. The check also lost a failure mode. Stale .tsbuildinfo files, left behind by a branch switch or a renamed checkout, used to produce hundreds of false type errors until someone deleted the cache. bun check keeps no state, so the check itself can no longer go stale.
Where tsc Still Wins
Not everywhere. tsc's incremental mode is very good at re-checking a project you just checked. On our heavy service, re-checking after a one-line change took tsc 0.81 s and bun check 1.57 s. With nothing changed, tsc answered in 0.15 s.
bun check has no incremental mode. Every run is a cold run, just a much faster cold run.
That defines the trade.
- CI runners and fresh worktrees always start cold. There,
bun checkwins on every measurement we took. - A developer re-checking one package in a loop was better served by a warm
tsc.
We made the switch because cold is what the gate actually runs. A fast, stateless check that every machine runs the same way was worth more to us than the warm-loop edge.
bun check has no language server either. Editors still run TypeScript's own, so this change does nothing for an editor that feels slow.
What We Learned Moving 92 Packages
The upgrade was mechanical. Getting the gate right took four lessons, and each one would have failed silently.
1. Pin the output format. Under an AI coding agent, bun check switches to an XML report. Our error-count ratchet read that as zero errors. Separately, it groups more than 50 identical errors into one entry, which undercounts any check that counts errors. Every type-check therefore runs with both flags:
"typecheck": "bun check -b --all --no-pretty"
A repository test fails if either flag is missing. The same audit found an older bug: tsc switches to coloured output whenever FORCE_COLOR is set, even to 0. That broke the same parser. --no-pretty overrides both.
2. A script named check shadows bun check. bun <name> runs a package script of that name before it reaches a built-in. We had 28 check scripts. In one service, check was tsc -b, so typing bun check there quietly ran the old checker. All 28 were renamed or folded into typecheck.
3. Deleting a phase deletes what it was checking. The declarations phase looked like plumbing. It turned out to be the only type-check two shared packages ever had. One contained a compile-time test written to catch a past outage. Removing the phase without noticing would have removed that test too.
The same phase hid a build-order dependency. It depended on 53 builds, so it happened to build four packages before they checked themselves. Each of the four imports its own published entry points, which resolve to its own build output. Without that ordering, one failed outright and three passed by scheduling luck.
Each now has an explicit edge, and a test asks the compiler where every self-import resolves. If an edge goes missing, or one is no longer needed, the test fails.
4. One exception, with an expiry date. In one project, bun check reports two "type instantiation is excessively deep" errors (TS2589) that tsc 7.0.2 does not. They are deterministic, and the minimal reproduction is two files.
That project stays on tsc. Its type-check also runs a guard that executes bun check on the reproduction. The guard fails once Bun stops reporting the error, so the exception cannot outlive the bug.
What This Means for Scrydon Users
Users will not see the type-checker directly. They will see how quickly a fix gets from a pull request into a release. Type-checking runs on every change, so a gate that runs cold in half the time, with a third less memory, shortens that path.
As with Bun 1.4, we will not claim more than we have measured. Until the gate has run in CI, these numbers describe our laptops.
What Comes Next
We kept CI type-check concurrency at two. That cap was set because tsc ran out of memory, and bun check now uses far less. Raising it is the obvious next step, but only once a CI run has measured it.
Bun 1.4.3 also adds bun test --check and bun build --check. Both make type errors fail a test run or a build directly, and we will evaluate whether they belong in our package pipelines. Bun also reports that servers use 67–89% less idle CPU than on 1.4.2. That is a runtime claim, so we will measure it the way we measured Bun 1.4.0: on staging, against the previous release, before saying anything about it.
For now, the result is simple. A type-checker that reports the same errors as tsc in 103 of our 104 projects, needs nothing built first and keeps no state is now our gate. The one exception is guarded and expires on its own.