Claude Code, Bun and TypeScript Anthropic shipped Claude Code, its agentic CLI coding assistant, on Bun instead of Node.js, a choice that highlights tradeoffs in startup latency, bundling strategy, and native module compatibility for TypeScript-heavy developer tooling. Bun's fast startup (5–15ms vs. Node's 50–80ms) and first-party bundler reduce cold-start lag and simplify distribution, though native addons using V8 internals may face compatibility issues. Anthropic recently shipped Claude Code — their agentic CLI coding assistant — on Bun instead of Node.js. For most product announcements, the runtime choice would be a footnote. Here it's worth unpacking, because the tradeoffs Anthropic navigated are exactly the ones you hit when building or evaluating TypeScript-heavy developer tooling: startup latency, bundling strategy, native module compatibility, and what "good enough" dependency management actually looks like in 2025. This isn't a Bun vs. Node benchmarking post. It's an examination of why the decision makes sense for a CLI tool specifically, what it signals about the broader ecosystem, and where the tradeoffs still bite you. For a long-running server process, Node.js startup cost of 50–150ms is irrelevant — you pay it once. For a CLI invoked dozens of times per development session, cold-start latency is a first-class UX concern. Bun's startup time is consistently in the 5–15ms range for a simple script. Node.js lands closer to 50–80ms before your first line of application code runs. That delta is imperceptible in a single invocation. Run a CLI 30 times in a session and you've saved a couple of seconds — more importantly, you've removed the subjective sense of lag that makes a tool feel heavy. This is the same reason Deno has gained traction in scripting contexts despite losing the server-side battle to Node. Fast startup is a feature, and for AI-assisted tooling where the human is waiting in a tight feedback loop, it matters. Bun ships a first-party bundler. For a CLI, this is significant. The standard Node.js distribution story for a TypeScript CLI involves: tsc or esbuild esbuild or rollup to collapse the dependency graph node modules large, fragile or use a tool like pkg or nexe to produce a single executableBun collapses steps 1–3 into a single command: bun build ./src/index.ts --compile --outfile claude-code The --compile flag produces a self-contained binary that embeds the Bun runtime. No Node.js installation required on the target machine. For a CLI distributed via npm npm install -g @anthropic-ai/claude-code , this matters less — you can assume Node is present. But for future distribution channels Homebrew, direct download, CI runner images , single-binary output is a meaningful operational simplification. Bun's bundler also strips unused exports aggressively. A TypeScript codebase pulling in large SDKs — the Anthropic SDK, tree-sitter bindings, various language servers — can produce a substantially smaller artifact than a naive tsc + ship-everything approach. Bun's package manager is faster than npm by a wide margin — typically 10–25× on a cold install, faster still on cache hits. For a tool like Claude Code that's installed globally and occasionally updated, this shows up as a noticeably snappier install. Bun uses a binary lockfile bun.lockb that's more compact and faster to parse than package-lock.json , but it's not human-readable. If you care about auditing lockfile diffs in code review, you'll need to run bun install --frozen-lockfile in CI and accept that the lockfile diff in your PR is opaque. More substantively: Bun is node modules -compatible. It doesn't invent a new module resolution scheme — your existing package.json works. Native modules compiled against Node's ABI mostly work, with exceptions for modules using non-public V8 internals. That's where the real compatibility risk sits. Bun uses JavaScriptCore JSC instead of V8. For pure JavaScript and TypeScript, this is transparent. For native addons compiled as .node files via node-gyp , the situation is more complicated. Modules that use the Node-API N-API surface — the stable ABI layer introduced specifically for this kind of portability — generally work fine under Bun. Modules that reach into V8 internals or use older nan -based bindings may not. For a coding assistant CLI, the relevant native dependencies are typically: Anthropic presumably validated these before shipping. But if you're evaluating Bun for your own TypeScript tooling and you depend on something like sharp , better-sqlite3 , or canvas , verify native compatibility explicitly before committing. // Quick compatibility check — run under both Bun and Node, compare output import { createRequire } from 'module'; const require = createRequire import.meta.url ; try { const nativeAddon = require './build/Release/addon.node' ; console.log 'Native addon loaded:', Object.keys nativeAddon ; } catch err { console.error 'Native addon failed:', err as Error .message ; } Bun executes TypeScript directly without a compilation step — no ts-node , no tsx , no esbuild-register . You write a .ts file and run it: bun run src/index.ts This is not TypeScript type-checking. Bun strips types and runs the resulting JavaScript. But for iterating on tooling internals, removing the compilation step from the inner loop is a meaningful quality-of-life improvement. For production builds of a distributed CLI, you still want tsc --noEmit as a type-check step in CI. The Bun model is: use JSC for fast execution, use TypeScript's type checker separately for correctness. These are separable concerns, and Bun's approach of not conflating them is arguably more honest than ts-node 's. A CI configuration that reflects this split: jobs: build: steps: - name: Type check run: npx tsc --noEmit - name: Test run: bun test - name: Build binary run: bun build ./src/index.ts --compile --outfile dist/claude-code The Claude Code runtime decision reflects a broader shift in how TypeScript-native tooling is evaluated. Two years ago, "build on Node.js" was the obvious default. Today, the question is more deliberate: what does this tool actually need from a runtime? For CLIs and developer tools specifically, the evaluation matrix looks like: Anthropic's choice is defensible on every axis that matters for a CLI: fast startup, TypeScript-native execution, and streamlined distribution. The tradeoffs they accepted — opaque lockfiles, some native module risk — are manageable at their scale and usage pattern. If you're maintaining or evaluating a TypeScript CLI or developer tool today: Audit your native dependencies first. List every package that includes a .node binary. Check the Bun compatibility tracker or run bun install && bun run src/index.ts against your entry point and see what breaks. Native compatibility issues surface immediately. Use Bun's test runner if you switch. bun test is Jest-compatible it understands describe , it , expect and significantly faster. It's the easiest win from a Bun migration. Keep tsc --noEmit in CI regardless. Bun's type-stripping is not type-checking. Don't let the absence of a build step create a false sense of type safety. The type checker is a separate tool; treat it as one. Single-binary output is worth evaluating seriously. If your CLI is distributed outside npm — as a GitHub release artifact, inside a Docker image, via Homebrew — bun build --compile simplifies your distribution pipeline in ways that matter operationally. The broader lesson from Claude Code's stack choice is that runtime selection for TypeScript tooling is now a real engineering decision with real tradeoffs, not a default. Node.js is still the right answer for many contexts. For a fast, TypeScript-heavy CLI where startup latency and developer ergonomics are primary concerns, Bun is increasingly a serious contender — and Anthropic just made that case with a high-visibility production deployment.