Cloudflare’s tsrs Turns an Agent-Written Compiler Port Into a Differential-Testing Bet, and the 8x Headline Depends on the Baseline
Cloudflare’s tsrs, a Rust port of the TypeScript compiler written by AI agents, printed identical diagnostics to the pinned native build on all 2,959 programs I compared, but its 8x speedup is...
Cloudflare said on October 20 that it is working with VoidZero and Max Schwenk of Owner.com on tsrs, a Rust port of the TypeScript compiler. Its announcement says the port is “up to 8x faster and uses ~70% less memory compared to TypeScript 7.0 when using the same number of type check workers.” It also explains why such a port exists now: “frontier AI models can now do large-scale ports almost independently in a matter of days.”
Table Of Content
- What the announcement says, and what it leaves to other pages
- How the port was built
- One session, one oracle
- What the commit log shows
- The oracle is the product
- What the Go code and the test suites cover
- Where the repository itself says the oracle stops
- A warning, not a gate
- Two safeguards that the repository shows as switched off
- I ran the oracle myself
- Method
- Result
- What this does and does not show
- What the 8x compares
- The post’s own charts
- The repository’s 17-project table
- The upstreaming claim
- What you can run today
- Packages and platforms
- Keeping up with TypeScript
- A cautious way to adopt it
- What I could not check
Two parts of that deserve a harder look. How does a compiler written by agents earn trust, and what exactly does the speed claim compare? I read the post against the tsrs repository, Schwenk’s write-up on the Owner engineering blog, the TypeScript team’s 7.1 beta announcement and the npm registry. Then I ran the pinned native TypeScript compiler and the tsrs WebAssembly build against each other on 2,959 programs, 2,956 of them mutated.
The short version: the two compilers printed identical diagnostics on every program I tried, and the 8x figure is real in Cloudflare’s own charts, but it is measured against the July release of TypeScript. Against the October 19 nightly build, the same charts work out to roughly 3x to 6x. On Windows x64, npm install tsrs does not yet deliver a native binary.
What the announcement says, and what it leaves to other pages
The Cloudflare post was published on October 20 at 13:00 UTC. Five of its statements matter to a team deciding whether to try tsrs.
- Speed and memory. The “up to 8x” and “~70%” figures, backed by two charts.
- Faithfulness. tsrs “aims to be a faithful port of TypeScript”, and “we are not forking TypeScript”.
- Upstream effect. The post says the next TypeScript is “up to 2.5x faster than TypeScript 7.0” and that “Some of these optimizations were upstreamed from tsrs, and we’re hoping to find more.”
- A known difference. There are differences from upstream, “like for example the order in which diagnostics are printed”.
- Maintenance. “We will apply the same process to TypeScript, taking extreme care that we don’t introduce diverging behaviors from upstream TypeScript.” The “same process” is the one Cloudflare described for its Vinext project: “Each morning, an agent reviews the changes, fetches diffs, and opens tracking issues for anything that could affect Vinext.”
The two charts have no alt text and no caption in the page’s HTML, so their numbers exist only as pixels. I read them off the images; the figures are in the tables further down. The post does not say what machine produced them or how many runs sit behind each bar. The repository’s benchmark page does say that for its own tables.
How the port was built
One session, one oracle
Schwenk, who is Head of AI Developer Experience at Owner, describes the start in his post. On September 30 he asked Claude Opus 5.5 for a port, and he is explicit about authorship: “I wrote zero code.” His account of the request is “I asked Opus for a faithful port of TypeScript Go to Rust”, with rules that include “Same algorithms, function structure, evaluation order and diagnostics, pinned to one upstream commit.” The tests were the specification: the TypeScript team’s Go port left behind 15,197 conformance cases, and he used them as the acceptance suite.
Then: “Around 1:30 AM I told it to work overnight until it had something working.” By his account, on the first end-to-end run “78% of TypeScript’s error baselines matched byte for byte, and another 21% only differed because there was no type printer yet.” After a type printer was added, the match rate reached 99.97%, “all but four of 13,462”.
What the commit log shows
The public history is consistent with that account. I cloned the repository’s history without file contents and read it.
- The first commit, titled “Workspace skeleton, arena pointer, porting guide”, is dated September 30 at 01:44 US Eastern time.
- A commit dated 15:35 the same day, 13 hours and 51 minutes later, is titled “docs: node builder live; conformance 13,398 pass / 2 codes / 62 fail / 0 crash; Project counters equal the reference”.
- The branch holds 1,801 commits, 309 of them on September 30 alone (UTC). 490 of the 1,801 (27.2 percent) carry a Claude co-author trailer, most of them naming Claude Opus 5.5. The trailer is the only authorship signal in the log, so it sets a floor, not a total.
- The source tree is about 22.6 MB of Rust in 843 files, from the GitHub tree listing.
Two caveats. The repository’s status log says it was deleted and recreated on October 1 from rewritten history, so the dates describe what survived the rewrite. And Schwenk had already opened performance pull requests against microsoft/TypeScript on September 27, so his working knowledge predates the first public commit.
The oracle is the product
An agent-written port is only as trustworthy as the thing that grades it. For tsrs the grader is the Go implementation of TypeScript at one pinned commit. The repository’s compatibility page puts the rule bluntly: “The Go code is the spec: any difference from it in diagnostics, their order, message text or exit code is a tsrs bug, even when tsrs’s output looks better.” That is the same idea as the differential testing we walked through in a Python tutorial, applied to a compiler.
What the Go code and the test suites cover
| Check | Result the repository reports |
|---|---|
| TypeScript error baselines | 13,458 of 13,462 match; the other four are described as “test-harness artifacts” |
.types and .symbols baselines |
all 12,779 match |
| JavaScript emit baselines | 13,392 pass, 0 fail |
| Language server (fourslash) groups | 4,074 of 4,546 pass through the Go runner; 472 are skipped |
--build scenarios |
189 of 192 tsbuild and 191 of 223 tsc baselines; the failures are CLI commands tsrs does not port |
| Top-repository parity job | documented as comparing diagnostics with the pinned Go build on 1,000 popular TypeScript repositories after compiler changes merge |
That is a strong test base, and it is public. The pinned commit is b85298b6a81f, which the repository says corresponds to the TypeScript nightly 7.1.0-dev.20260930.4.
Where the repository itself says the oracle stops
The documents do not all say the same thing about correctness. The npm README says: “In our testing on existing projects, we have yet to find a correctness bug in tsrs.” The compatibility page says “top-repository comparisons still report known parity gaps and tsrs failures”. And the benchmark results page labels one 16-checker result on drizzle-orm as “Same count, different locations (a tsrs bug)”. Schwenk is the most direct: “There’s still no proof of parity, just a lot of ways for a regression to get noticed.”
A warning, not a gate
The parity job reports differences without failing. Its documentation says the report command exits with a special status for parity findings and “Actions shows a warning and keeps the workflow green.” The latest completed run on main when I looked, run 39551485651 on October 21, did exactly that. Its report job carried the annotation “Repository comparison found differences; see the job summary.” and the run concluded as a success. The run page shows that annotation but asks for a sign-in to read logs and the report artifact, so I do not know how many repositories differed.
Two safeguards that the repository shows as switched off
Schwenk’s post says: “A nightly job also throws 200,000 mutated inputs at the parser and records anywhere it disagrees with the reference.” The workflow for that, fuzz.yml, runs tools/fuzz/parser.py for 50 rounds of 4,000 inputs, which is 200,000. On October 16 a commit titled “ci: simplify CI and disable auxiliary workflows (#570)” renamed it to fuzz.yml.bak. GitHub’s workflow syntax reference says workflow files “must have either a .yml or .yaml file extension”, and the only scheduled workflow left at the head of main is a daily DefinitelyTyped conformance run. I cannot tell from outside whether the fuzz job runs somewhere else.
The same commit also added benchmark-comparison and pull-request-verification workflows under .bak names, and the results page opens with “Benchmark CI is disabled.” That page is a dated snapshot, so it is honest about its age, but it means the performance numbers are not being regenerated on every change.
I ran the oracle myself
If the Go build is the answer key, anyone can grade tsrs against it. I did that on a Windows 11 machine.
Method
- Oracle. The native compiler from the npm package
@typescript/typescript-win32-x64at7.1.0-dev.20260930.4, which is the nightly the tsrs repository says matches its pin. It reportsVersion 7.1.0-dev.20260930.4. - Subject. The tsrs WebAssembly build,
@tsrs/wasm0.18.0. There is no Node on this machine, so I wrote a small host in Python with wasmtime 49.0.0, following the interface in the package’score.js. Its--versionprintsVersion 7.1.0-dev.20260930.4 (tsrs 0.18.0-ts7.1.0-dev.20260929, microsoft/TypeScript@b85298b6a81f). - Corpora. Three real projects from their default branches on October 21: type-fest (254 declaration files, 763 KB, commit
cc3e31f2cc), ts-pattern (20 files, 132 KB,f11be73acb) and typebox (709 files, 2.4 MB,c38167da3c). Options werestrict,skipLibCheck: false,lib: es2023and no@types. - Mutations. For each project, the unmodified program plus 1,000 seeded mutants. Each mutant changes one file, only outside comments: delete or duplicate a token, replace an identifier with another from the same file, swap a keyword (
stringfornumber,extendsfor|, and so on), delete, duplicate or swap a line, delete a short span, or insert one of 17 snippets that provoke circular types, deep recursion and duplicate exports. - Comparison. The native compiler ran single-threaded; both printed diagnostics with
--pretty false. I compared full stdout and the exit status, after replacing the project directory in native paths with the WebAssembly host’s/proj.
The core of the host is the request protocol that core.js documents by example. The compiler reads files through an imported tsrs_host.fs function, so the project lives in a dictionary and nothing touches disk:
# tsrs.wasm 0.18.0 request protocol, as used by core.js (flags: 1 JSON, 2 case-insensitive, 4 tty)
req = "\0".join([cwd, "0", *args]).encode("utf-8")
ptr = exports["tsrs_input"](store, len(req))
memory.write(store, req, ptr)
exit_code = exports["tsrs_run"](store)
Result
2,959 of the 3,003 runs produced a comparison; in the other 44 a mutation changed nothing or had no candidate. All 2,959 were identical in output and exit status. Together they printed 51,919 diagnostic lines on each side. 2,816 runs ended with errors (status 2) and 143 were clean (status 0), the same on both sides. The WebAssembly build never trapped and never wrote to stderr. In 2,566 of the 2,956 mutants the number of diagnostics differed from the unmutated project, so most mutations did change the output being compared.
I also measured what the mutants exercise. In a native-only re-run of every third task (981 programs) they produced 94 distinct error codes: 41 in the TS1xxx syntax range, 47 in the TS2xxx range and 6 elsewhere. The most common were TS1005 (420 programs), TS2304 (238), TS1128 (213), TS2307 (171) and TS2344 (157). Assignability errors (TS2322) appeared in 18 of the 981.
Two checks on the harness itself. As a negative control, I fed tsrs the unmutated source while the native compiler checked the mutated source: the harness flagged 255 of the 255 mutants whose native output changed, so it can see a difference. And I ran the native compiler 24 times on each unmutated project at its default checker count, at 2, at 8 and single-threaded. Each setting gave one distinct output. The repository says Go’s own output can depend on thread timing; I did not see that on these three projects.
What this does and does not show
It supports the claim that the WebAssembly build matches the pinned Go compiler on diagnostics. That is a meaningful result for a compiler whose public history starts three weeks ago. It does not test the native multi-threaded binary, which is where the checker scheduling, work stealing and memory behavior behind the speed claims live. My mutations lean on syntax recovery and name resolution, with thin coverage of assignability and deep instantiation. I compared diagnostics and exit status only, not emitted JavaScript, declaration files or language server answers. And three projects are not the 1,000 repositories in the project’s own job.
What the 8x compares
The post’s own charts
The first chart, titled “TypeScript Default Config”, runs all three compilers with -p <project> --noEmit --incremental false --pretty false --checkers 4. It compares tsrs 0.18.0 (pinned to a September 29 commit) with TypeScript 7.0.2 and with the TypeScript nightly 7.1.0-dev.20261019.1. The ratios Cloudflare prints are all relative to 7.0.2. The last column is my division of the printed, rounded times, so it carries a few percent of rounding error, most of all on the 0.23 second webpack bar.
| Project | tsrs | Nightly 20261019.1 | TypeScript 7.0.2 | tsrs vs 7.0.2 (printed) | tsrs vs nightly (mine) |
|---|---|---|---|---|---|
| VS Code | 1.73 s | 5.28 s | 6.62 s | 3.83x | 3.05x |
| MUI docs | 1.29 s | 4.05 s | 10.44 s | 8.10x | 3.14x |
| webpack | 0.23 s | 0.71 s | 0.91 s | 4.03x | 3.09x |
| t3code server | 1.21 s | 7.17 s | 9.82 s | 8.08x | 5.93x |
So “up to 8x” is the best two of four projects against the July release. The second chart, “Maximum resources” with --checkers 14, gives 4.51x, 8.65x, 4.77x and 8.02x against 7.0.2 and, by my division, 3.85x to 4.56x against the nightly.
Memory tells the opposite story about wording. Against 7.0.2 the printed peak memory ratios in the first chart are 0.23, 0.13, 0.31 and 0.18, which is 77, 87, 69 and 82 percent less. So “~70% less memory” is the smallest saving on those projects, not the typical one. Against the nightly, my division gives 67, 67, 56 and 73 percent less at four checkers.
The repository’s 17-project table
The repository’s results page is broader: 17 projects on a 16-vCPU Linux runner, median of 10 interleaved runs (3 for tsgo). At --checkers 4 I compute from its table that tsrs is 4.37x to 11.81x faster than tsgo 7.0.2, with a median of 5.79x, and uses 2.19x to 7.24x less memory, with a median of 3.49x. The page notes that the baseline is 7.0.2 while tsrs ports a later 7.1-dev commit, and that the two print different error counts on 8 of the 17 projects at four checkers. Schwenk’s own summary of the suite is narrower: “Across that suite tsrs uses about 3× less memory and runs 2.5 to 5× faster than the nightly”.
These sources agree once the baseline is named. Against 7.0.2 the speedup looks bigger because 7.0.2 predates the TypeScript team’s own optimization work, which the nightly already carries. The 7.1 beta post lists dozens of optimizations, including “Optimize Representations of Checker-Specific Side-Tables” and “Faster Union Type Construction”, but it gives no speedup figure for 7.1. The chart’s nightly is 2.58x faster than 7.0.2 on MUI docs, which is where “up to 2.5x” appears to come from.
The upstreaming claim
The TypeScript beta post does not mention tsrs or Rust. It links 57 pull requests to microsoft/TypeScript and typescript-go, and I found no pull request by Schwenk among them. He has eight pull requests against microsoft/TypeScript since September 27. Two are merged: #64521, “check intersection props for never-reduction in a stable order”, a seven-line change merged on September 29, and #64728, “Cache undefined widening properties per symbol, not per name”, merged on October 18. Two are open, including #64475, and four were closed without merging. Neither merged pull request appears in the beta post’s list. That does not disprove “some of these optimizations were upstreamed”, because the TypeScript team may have adopted ideas rather than patches, but the public record shows a modest direct flow so far.
What you can run today
Packages and platforms
The tsrs package on npm lists five optional platform packages: macOS arm64 and Linux x64 and arm64 (glibc and musl). The registry has no @tsrs/win32-x64. The compatibility page confirms it: “Windows x64 is configured for the next release; 0.18.0 and earlier have no Windows package.” A commit titled “feat: ship Windows x64 binary (#636)” landed on October 20, so the next release should fix this, but 0.18.0 is still the latest version on npm as I write. Reading the launcher’s code, an unsupported platform raises an error that names the supported packages and suggests setting TSRS_BINARY to a locally built executable.
The WebAssembly package is the fallback. It is single-threaded, targets Node 22 and browsers, and is documented as byte-identical to native --singleThreaded output on a 2,000-case conformance sample. The compatibility page also lists gaps: --init and --showConfig are not supported, --generateTrace only prints a warning, and watch mode does not run on Windows.
Keeping up with TypeScript
The pin is the part to watch. tsrs targets a commit from September 29, while the nightly in Cloudflare’s chart is from October 19 and the beta was tagged on npm on October 20. Upstream changes since the pin have to be ported to keep parity. The maintenance plan in the post is in the future tense, and I found no scheduled upstream-sync workflow in the repository: its triage workflow handles issue reports, and the one active schedule is the daily DefinitelyTyped run. The daily agent review that Cloudflare describes for Vinext, which tracks Next.js, is the model, not yet a visible feature of this project. VoidZero, the maker of Oxc, is one of the JavaScript-tooling teams Cloudflare has been buying, as we noted when it acquired Deno.
A cautious way to adopt it
I could not run the native binary on this Windows machine, so this recipe is the shape of the check, not output I captured. On Linux or macOS, install tsrs next to the exact TypeScript build it pins, use the two flags the README recommends for the closest comparison, and diff everything:
npm install --save-exact --save-dev [email protected] [email protected]
npx tsc -p . --noEmit --pretty false --singleThreaded > tsc.txt; echo "tsc exit $?"
npx tsrs -p . --noEmit --pretty false --checkerAssignment go --noLazyMembers > tsrs.txt; echo "tsrs exit $?"
diff -u tsc.txt tsrs.txt && echo identical
- Compare against the pinned TypeScript, not the latest. The repository’s own table shows how a newer or older baseline changes error counts.
- Run it as a non-blocking CI job first. Treat any diff as a bug report for the project, as the compatibility page defines it, and decide who triages it.
- Keep
tscfor emit until you have diffed emitted JavaScript and declaration files on your own code. - Pin exact versions. The version string embeds a nightly, and releases have come about every day or two.
- Check your platforms. Windows developers need the next release or the WebAssembly package.
The larger point is about agent-written software. A port like this is cheap to produce and expensive to trust, and the review burden moves from reading code to maintaining the oracle: a pinned reference, acceptance suites that someone else wrote, and a comparison on real code that fails loudly. That matches what Harvard’s study of 718 firms suggested about where AI coding agents move the bottleneck. For tsrs the oracle exists and is public. What is missing is a gate that stops on a difference, a visible sync cadence and a Windows build.
What I could not check
- The Owner numbers (80 seconds to 7 on a monorepo of about 37,000 files) rest on private code that no outsider can run.
- The charts’ hardware and run counts are not stated in the post, and I did not reproduce any timing on a native tsrs binary.
- The results of the 1,000-repository parity job are in a job summary I could not read.
- Whether the parser fuzz job runs somewhere other than the renamed workflow.
- Anything about emit, the language server or multi-threaded behavior.








No Comment! Be the first one.