DEV Community

Sangmin Lee
Sangmin Lee

Posted on • Originally published at claudeguide.io

Claude API on Bun: 3 Faster Cold Starts (2026)

Originally published at claudeguide.io/claude-api-bun-runtime

Claude API on Bun: 3× Faster Cold Starts (2026)

Can I run the Claude API on Bun? Yes — @anthropic-ai/sdk has worked on Bun since SDK v1.0 (mid-2024) and runs unmodified on Bun 1.2+. In our local benchmarks on an M4 Mac mini, Bun cold-starts a Claude API process roughly 3× faster than Node.js 22 (about 47ms vs 142ms to first token request) and warm calls are ~2× faster. There is one real caveat: Bun's fetch implementation has had subtle differences in streaming abort semantics, so streaming + AbortController deserves a closer look. Below: the working code, the numbers, the gotchas, and the deploy targets that actually matter.

How to call Claude API from Bun

Bun ships its own runtime, package manager, and TypeScript executor. You don't need tsx, ts-node, or a tsconfig.json to run a .ts file. Install the SDK with Bun's package manager:

bun init -y
bun add @anthropic-ai/sdk
Enter fullscreen mode Exit fullscreen mode

Then a complete hello.ts:

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY,
});

const start = performance.now();

const message = await client.messages.create({
  model: "claude-sonnet-4-5",
  max_tokens: 256,
  messages: [
    { role: "user", content: "In one sentence: what is Bun?" },
  ],
});

console.log(message.content[0].type === "text" ? message.content[0].text : "");
console.log(`elapsed: ${(performance.now() - start).toFixed(0)}ms`);
Enter fullscreen mode Exit fullscreen mode

Run it directly:

ANTHROPIC_API_KEY=sk-ant-... bun run hello.ts
Enter fullscreen mode Exit fullscreen mode

No build step. No tsc. No node --loader. The single-binary story is the headline reason teams switch — but the runtime behavior underneath is where the cold-start delta comes from.

Cold-start benchmarks: Bun vs Node.js

We measured a minimal "import SDK, send one request, read the response" script on the same machine (M4 Mac mini, 32 GB, macOS 15.4) over 50 runs each. Cold start = process spawn to first byte returned from messages.create. Warm = re-using the same process for the next call.

Metric Node.js 22.13 Bun 1.2.10 Delta
Cold start (p50) 142 ms 47 ms 3.0× faster
Cold start (p95) 188 ms 71 ms 2.6× faster
Warm call (p50) 28 ms 12 ms 2.3× faster
RSS after 1 req 86 MB 41 MB 2.1× lower
bun add vs npm i 11.4 s 0.9 s 12× faster

The cold-start gap is mostly module resolution and TS transpilation overhead. Node has to pass .ts through tsx or a loader; Bun parses TS natively in its JavaScriptCore-based runtime. For long-running servers the cold-start win amortizes to zero — but for scheduled jobs, CLI tools, queue workers that scale to zero, and serverless containers, the delta is real money. A nightly batch that fires 200 invocations saves ~19 seconds of wall clock and proportional CPU billing.

The warm-call delta is smaller and dominated by network RTT to api.anthropic.com, so don't expect Bun to magically halve your inference latency. It won't. The runtime overhead is a few percent of the total in steady state.

The fetch caveat

The Anthropic SDK uses the platform fetch. Bun's fetch is not Node's undici — it's a custom implementation written in Zig. For 95% of workloads they behave identically. The 5% that has bitten people:

  1. Streaming abort semantics. Calling controller.abort() mid-stream in Bun used to leave the underlying TCP socket in a half-closed state on some versions. Fixed in Bun 1.1.30+, but if you're on an older Bun and you abort a long Claude stream (e.g. user navigated away), verify the connection actually closes. Add a signal to every streaming call and test with lsof -p $(pgrep bun) if you suspect leaks.
  2. Response.body lock contention. Bun is stricter about reading the body twice. If you log response.body and then pass it to the SDK, you'll get a "stream already locked" error that Node tolerated.
  3. Custom fetch injection. If you pass your own fetch to new Anthropic({ fetch: myFetch }) for retry/proxy logic, Bun's runtime gives you globalThis.fetch from Bun, not undici. Most polyfills work, but node-fetch@3 will not — drop it and use the platform fetch.

For a streaming example that handles abort cleanly, see our streaming guide. The pattern below is the minimal Bun version:

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const controller = new AbortController();

// Optional: abort if user disconnects, timeout fires, etc.
setTimeout(() =

## Package compatibility: @anthropic-ai/sdk on Bun

The official SDK lists Node 18+ as supported, but Bun has been a tested target since SDK v1.0 (the rewrite that moved off `node-fetch`). Concretely:

| Feature | Bun status | Notes |
|---|---|---|
| `messages.create` | Works | Identical to Node |
| `messages.stream` | Works | Use `signal` for abort safety |
| Tool use / function calling | Works | No runtime differences |
| Prompt caching | Works | `cache_control` headers passthrough is fine |
| Files API | Works | `Bun.file()` returns a `Blob`-compatible value |
| Batch API | Works | JSONL streaming behaves correctly |
| Web search tool | Works | Server-side, runtime-agnostic |
| `extra_headers` | Works | Useful for [error handling](/claude-api-error-handling) middleware |

Two ecosystem packages worth noting:

- **`@anthropic-ai/vertex-sdk`**  works on Bun. The Google auth flow uses `google-auth-library`, which is pure JS and runtime-agnostic.
- **`@anthropic-ai/bedrock-sdk`**  works, but AWS SDK v3 is heavy (~30 MB resolved). Bun's package install speed makes the bloat less painful than under npm.

If you wrap the SDK with retry logic or [cost monitoring](/claude-api-cost-monitoring-guide), test under both runtimes before shipping. The differences are rare but real, and "works on my Mac under Bun" is a known failure mode when production runs Node.

## When NOT to use Bun

Bun's wins evaporate in two important cases:

1. **Vercel Edge / Cloudflare Workers.** These are V8 isolates running Web Standards APIs  there's no process spawn, no module load, no TS transpile. Cold start is in the single-digit milliseconds because the isolate is pre-warmed. Bun adds nothing here; you're not even running Bun, you're running V8 with the Workers runtime. Use the SDK as-is, deploy with `wrangler` or `vercel deploy`, and ignore this article.
2. **AWS Lambda with provisioned concurrency.** If you've already paid for warm instances, the Node 22 runtime is a known quantity, has first-class CloudWatch integration, and the cold-start savings don't apply. Bun on Lambda is possible via custom runtime layers, but the operational surface area isn't worth a 100ms saving you don't need.

Where Bun shines: **long-running servers (`bun --hot`), scheduled jobs, scale-to-zero containers, CLI tools, and local dev**. The local-dev story alone (no `tsx`, instant install, built-in `.env`, built-in test runner) is enough for most teams.

## Production deployment options

Bun is a first-class runtime on most modern platforms now. Concrete options as of May 2026:

| Platform | Bun support | Notes |
|---|---|---|
| **Railway** | Native | Auto-detects `bun.lockb`; sets `bun start` |
| **Fly.io** | Native | Use `flyctl launch` with the Bun preset |
| **Render** | Native | "Bun" runtime in the dashboard |
| **Northflank** | Native | Buildpack autodetect |
| **Docker anywhere** | Use `oven/bun:1.2-alpine` | 80 MB image vs ~150 MB for `node:22-alpine` |
| **Vercel (Node functions)** | Build-time only | Functions still run on Node |
| **Cloudflare Workers** | N/A | Workers runtime, not Bun (and that's fine) |

A minimal `Dockerfile` for a Bun + Claude service:

Enter fullscreen mode Exit fullscreen mode


dockerfile
FROM oven/bun:1.2-alpine
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production
COPY . .
ENV NODE_ENV=production
EXPOSE 3000
CMD ["bun", "run", "src/server.ts"]




Image size, install time, and cold start all tilt in Bun's favor. The ops cost story is small but compounds — if you run 50 small services, Bun's lower memory floor (~40 MB vs ~85 MB per process) lets you pack roughly 2× more on the same VM.

## Frequently Asked Questions

### Does the Anthropic SDK officially support Bun?

The SDK's stated support matrix is "Node 18+, Deno, Bun, modern browsers." Bun has been continuously tested against the SDK since v1.0. There is no separate `@anthropic-ai/sdk-bun` package — the same package works everywhere.

### Can I use prompt caching from Bun?

Yes. Prompt caching is a server-side feature controlled by request headers and `cache_control` blocks in the message body. The runtime is irrelevant. Bun has no effect on cache hit rate; that's a function of how you structure your prompt prefix.

### Will Bun reduce my Claude API bill?

No, not directly. Token counts and per-token pricing are identical regardless of runtime. Bun reduces *infrastructure* cost (compute, memory, cold-start latency on scale-to-zero platforms), not API cost. To cut the API bill itself, prompt caching and model routing are the real levers.

### Is `bun --hot` reliable enough for production Claude services?

`bun --hot` is for development — it reloads on file change. For production, use plain `bun run` under a process supervisor (systemd, PM2, or your platform's default). `--hot` keeps state across reloads, which can mask bugs that surface in a fresh process.

### What about Deno for Claude API workloads?

Deno also runs the SDK fine and has similar cold-start characteristics to Bun (both skip the TS-transpile step Node requires). Bun's edge over Deno is npm compatibility — most teams have a `package.json` and don't want to rewrite imports. If you're greenfield with no npm dependencies, Deno is competitive. Otherwise, Bun is the lower-friction pick.
Enter fullscreen mode Exit fullscreen mode

Top comments (0)