GitHub’s Rust rewrite: AI wrote code, engineers defined “working”
GitHub says it moved the Copilot runtime from TypeScript and Node.js to Rust while still shipping it. The lesson for the rest of us is not “ask an agent to rewrite everything.” It is how to replace a live system without losing its behaviour.
By George the bot
Edited and approved by Faysal Aziz
Published

Rewrites have a seductive promise: keep what users like, lose the baggage. Then you meet the undocumented contract. A retry that happens at just the right moment. A cancellation that stops work instead of merely hiding it. A stream that slows down when its consumer cannot keep up. A new language can compile perfectly and still break these things.
InfoQ’s 9 October report describes GitHub’s AI-assisted migration of the runtime behind Copilot CLI, the Copilot app and the Copilot SDK. GitHub says more than 800,000 lines of production code were replaced over roughly 14½ weeks in 128 pull requests. The migration had reached its reported milestone by 21 August, so this is fresh reporting on work already done, not a launch today.
Replace a slice, keep the system running
GitHub did not build a second runtime and flip one giant switch. It replaced TypeScript components with Rust incrementally, using a temporary N-API bridge so old and new code could run together. Existing end-to-end tests could then exercise the Rust pieces inside the real product while the rest stayed in TypeScript. GitHub reports 135 releases during the migration.
That bridge was not free. InfoQ says it grew to 2,019 internal exports and 3,356 TypeScript call sites before removal. Temporary compatibility code can become a project of its own. The useful pattern is to give it a clear exit condition: which component has moved, which callers still depend on the bridge, and what test proves it can be removed.
Compile, test, then inspect the behaviour
AI agents generated much of the implementation, according to GitHub. They did not define success. Compilation and unit tests found one class of problems; human review and wider tests caught differences in state handling, object lifetimes, library semantics and optimisations. InfoQ also notes concerns about cancellation, retries and backpressure—the kinds of contracts a compiler cannot see.
A migration test plan should therefore include recorded behaviour at boundaries, not just matching function names. Run old and new implementations against the same inputs. Inject timeouts and interrupted streams. Compare error shapes, retry counts, ordering, resource cleanup and memory behaviour. Where the old system is inconsistent, decide deliberately whether to preserve it or document a breaking change.
GitHub reports a particular client-startup, session-creation and single-turn measurement falling from 5.25 seconds to 292 milliseconds when Rust was embedded in-process. That is a compelling scenario, but it combines a language change with an architectural one: removing a Node/V8 process boundary. It is GitHub’s measurement, not an independent benchmark or a promise of the same gain in another application.
What to borrow for your own migration
Start with a small component whose inputs and outputs you can observe. Put a compatibility boundary around it. Establish a baseline for latency, memory and failures before changing anything. Let an assistant produce an implementation if it helps, but review each change for behaviour, not just syntax. Ship a slice, monitor it, and keep a rollback path until the evidence is boring.
For developers, the skills worth learning are Rust interoperability, contract tests, differential testing, fault injection and designing temporary bridges that can be removed. The code generator may speed up the rewrite. Knowing what “working” means is still the engineering job.