Skip to content
InnovateTechie
Claude Code

Fix 'ReadableStream is not defined' with the Claude SDK

InnovateTechieBy InnovateTechie12 min read
Share
Developer terminal showing the readablestream is not defined error from the Claude SDK

Quick answer

When the Claude SDK throws `readablestream is not defined`, it means your JavaScript runtime does not expose the global `ReadableStream` object that the SDK's streaming code needs. The real fix is upgrading Node.js to a current LTS (18, 20, or newer). Polyfilling is a stopgap for environments you cannot upgrade.

If you have seen ReferenceError: ReadableStream is not defined in your terminal while calling @anthropic-ai/sdk, you are not looking at a bug in your own code or in Anthropic's library. You are looking at a runtime that is missing a Web platform API the SDK assumes is present. This guide explains exactly why the readablestream is not defined error happens, then walks through the fixes in the order you should try them — from the correct, durable fix to the quick workarounds.

Everything here is a general troubleshooting pattern, so it stays useful no matter which SDK version you are on. The error is also model-agnostic: Claude currently spans models like Opus 4.8 and Sonnet 4.6, and every one of them trips the same missing global, because the problem is your runtime rather than the model you call. If you are brand new to calling Claude programmatically, our Claude API getting started guide is a gentler on-ramp, and this page picks up where a plain "it won't run" leaves off.

What the readablestream is not defined error actually means

ReadableStream is part of the Web Streams API — the same streaming primitive browsers use for fetch() response bodies. When you ask the Claude SDK for a streaming completion (so tokens arrive incrementally instead of all at once), the SDK builds its stream on top of the global ReadableStream constructor.

Here is the catch: ReadableStream is a global in browsers and in modern Node, but it was not always a global in Node.js. When your runtime does not put ReadableStream on the global scope, the SDK's reference to it evaluates to nothing, and the engine throws ReferenceError: ReadableStream is not defined. That is the whole story. The readablestream is not defined message is a plain "this global does not exist here" error, not a network, auth, or quota problem.

Two facts make this concrete:

  • Node.js added ReadableStream as a global in Node 18. It arrived as an experimental global in the Node 18 line and has been available as a global in Node 18 and every release since (20, 22, and newer). On Node 16 and earlier, there is no global ReadableStream at all.
  • Some non-Node runtimes and bundlers do not expose it either. Edge functions, certain serverless targets, older webpack build targets, and some test environments can strip or fail to provide the global — even when the underlying platform technically supports Web Streams.

So the readablestream is not defined error tells you where your code is running matters as much as what it runs.

Diagram showing why readablestream is not defined appears across Node versions and runtimes

Which environments break, and why

Because this is a runtime-capability problem, the fix depends on where the Claude SDK Node error shows up. Use this table to identify your situation before you change anything.

EnvironmentWhy readablestream is not defined happensFix
Node.js 16 or earlierNo global ReadableStream exists in this Node lineUpgrade to Node 18/20+ (best) or polyfill
Node.js 18/20+ReadableStream is a global — error usually means a stale binary or wrong node on PATHConfirm node -v; fix your version manager
Edge / serverless runtimeRuntime may not expose Web Streams globalsChoose a Node runtime, or polyfill before SDK import
Cloudflare WorkersDifferent global surface; streaming globals varyUse the Workers-compatible setup; verify globals
Older webpack / bundler targetBuild target predates Web Streams globalsRaise the target; add a polyfill in the entry file
Jest with jsdom test envjsdom does not provide ReadableStreamSwitch testEnvironment to node, or polyfill in setup

The pattern is the same everywhere: the SDK expects a global that this specific runtime does not hand it. Once you know which row you are in, the fix is short. If your error is actually a different startup failure — the CLI, not the SDK — see Claude native binary not installed, which covers a separate "won't start" class of problem people often confuse with this one.

The correct, permanent fix for readablestream is not defined is to run the Claude SDK on a Node.js version that provides the global. Any current LTS does: Node 18, 20, or newer. This is the fix Anthropic's TypeScript SDK is built and tested against, and it removes the problem without adding any code to your project.

First, check what you are actually running:

node -v

If that prints v16.x or lower — or nothing, because node is not on your PATH — that is your answer. Upgrade using a version manager so you can switch cleanly:

# Install a current LTS with nvm and make it the default
nvm install 20
nvm use 20
nvm alias default 20
 
# Confirm the new version is active
node -v   # should print v20.x (or your chosen LTS)

On Windows, nvm-windows uses the same nvm install / nvm use commands. After switching, delete node_modules, reinstall, and re-run. A surprising number of readablestream is not defined reports come from a shell that still points at an old Node even though a newer one is installed — always re-check node -v in the same terminal that runs your app. If Claude Code itself is your entry point rather than a script, our note on Node.js for Claude Code covers picking a version that keeps the whole toolchain happy.

Upgrading Node is not just about this one error. Newer Node lines ship the stable fetch, ReadableStream, TextEncoder, and other Web APIs the modern SDK leans on, so upgrading clears a whole category of "global is not defined" failures at once.

Fix 2 (stopgap): polyfill ReadableStream

If you genuinely cannot upgrade Node right now — a locked corporate image, a legacy service, a platform you do not control — you can polyfill the missing global. Node has shipped a Web Streams implementation in its built-in stream/web module since well before it exposed the global, so on many older-but-not-ancient setups you can borrow it.

The key rule: the polyfill must run before the Claude SDK is imported. If the SDK loads first, it has already tried to reference the missing global and thrown. Put this at the very top of your entry file, above any import Anthropic from '@anthropic-ai/sdk':

// polyfill.mjs — import this FIRST, before the Claude SDK
import { ReadableStream } from 'node:stream/web';
 
if (typeof globalThis.ReadableStream === 'undefined') {
  globalThis.ReadableStream = ReadableStream;
}
 
// only now is it safe to import the SDK
// import Anthropic from '@anthropic-ai/sdk';

If your Node is old enough that node:stream/web is unavailable or incomplete, install a dedicated polyfill package instead:

npm install web-streams-polyfill
import { ReadableStream } from 'web-streams-polyfill';
globalThis.ReadableStream = ReadableStream;

Ordering is everything with polyfills. In an ESM project, imports are hoisted and evaluated before other top-level code, so putting the assignment "above" the SDK import in the same file is not enough — the SDK import can still run first. The clean pattern is a separate polyfill.mjs that you import as the very first line, or use Node's --import ./polyfill.mjs flag so the polyfill is guaranteed to load before your app graph. Treat this as a bridge, not a destination: it papers over the readablestream is not defined error but leaves you on an old runtime that will keep surfacing similar gaps.

Code comparison of upgrading Node versus polyfilling to fix readablestream is not defined

Fix 3: make your bundler or runtime provide Web Streams

When the ReferenceError: ReadableStream is not defined appears on Node 18+ — where the global should exist — the runtime or build step is stripping or replacing it. This is common in edge functions, Cloudflare Workers, older webpack targets, and test runners.

For edge and serverless functions, check which runtime the platform assigns to your function. Many providers offer both an "edge" runtime with a trimmed global surface and a full "Node" runtime. Streaming with the Claude SDK is safest on the Node runtime; if you must use edge, confirm the platform documents Web Streams support and test a tiny stream before shipping.

For webpack and other bundlers, raise your build target so the tool stops assuming an ancient environment, and avoid polyfill configs that shim ReadableStream to undefined. A modern target lets the native global pass through untouched.

For Jest, the jsdom test environment does not implement ReadableStream, so tests that import the SDK throw the Claude SDK Node error even though your app code is fine. Either set testEnvironment: 'node' for the affected files, or register a polyfill in your Jest setup file so the global exists before the SDK loads. Rate-limit and retry behavior can also differ in tests, so it is worth understanding how the SDK handles pushback in general — Claude API rate limits explains that side.

Fix 4 (workaround): stop streaming if you don't need it

If none of the above is available and you only need the finished response, you can sidestep the streaming code path entirely by requesting a non-streamed completion (stream: false, or simply not calling the streaming helper). Without a stream, the SDK never touches ReadableStream, so the readablestream is not defined error cannot fire.

This is a real workaround, but be honest about the trade-off: you lose incremental token delivery, which matters for chat-style UIs and long responses where users expect text to appear as it is generated. Disabling streaming to dodge a missing global is treating the symptom. If you later re-enable streaming without fixing the runtime, the error returns — and you may hit unrelated streaming issues like a stalled connection, which we cover in Claude API stream idle timeout. Upgrading Node remains the better answer; use non-streaming only as a temporary bridge while you plan the upgrade.

A quick decision path

Put together, the fixes form a simple order of preference:

  1. Run node -v first. It takes a couple of seconds and tells you which of the cases below you are actually in.
  2. On Node 16 or below, upgrade to a current LTS. That is the entire fix, and you are done.
  3. On Node 18+ and still seeing readablestream is not defined, suspect your runtime or bundler. Pick the matching row in the table above and configure that environment to expose Web Streams.
  4. Reach for a polyfill only when upgrading is genuinely blocked, and disable streaming only when you can accept losing incremental output.

That ordering matters because each lower rung adds maintenance you will have to remember later. A polyfill is one more file that must load first, forever. A node runtime override is one more platform setting to keep in sync. An upgraded Node version, by contrast, is invisible after you do it once. If you are also exploring Claude beyond the raw SDK, what is Claude Code shows the CLI side of the ecosystem, which ships its own runtime expectations you will want to satisfy the same way.

Frequently Asked Questions

Because the Claude SDK builds its streaming responses on top of the global ReadableStream object. Non-streaming calls never reference it, so they run fine even on a runtime that lacks the global. The moment you request a stream, the SDK touches ReadableStream, and if it is missing you get the readablestream is not defined error.

Use a current LTS — Node 18, 20, or newer. ReadableStream became a global in the Node 18 line and has shipped in every release since. On Node 16 and earlier there is no global ReadableStream, which is the most common cause of this error. Always confirm with node -v in the same terminal that runs your app.

It works, but treat it as a stopgap. A polyfill from node:stream/web or web-streams-polyfill fills the missing global, yet it leaves you on an old runtime that will keep surfacing similar "global is not defined" gaps. It is fine as a bridge while you schedule a Node upgrade, which is the durable fix.

Your runtime or build step is stripping the global. This shows up in edge/serverless runtimes, Cloudflare Workers, older webpack targets, and Jest's jsdom environment. Switch to a Node runtime, raise your bundler target, or set testEnvironment: 'node' so the native ReadableStream global reaches the SDK.

It avoids the error by never using the streaming code path, but it is a workaround, not a fix. You lose incremental token delivery, and the error returns the instant you re-enable streaming on an unpatched runtime. Upgrading Node is the correct permanent solution.

Jest's default jsdom test environment does not implement ReadableStream, so importing the SDK inside a test throws even though your real app runs on Node with the global present. Set testEnvironment: 'node' for those files, or add a polyfill in your Jest setup that defines the global before the SDK is imported.
InnovateTechie

Written by

InnovateTechie

Writing about Claude and the Anthropic toolkit — models, Claude Code, pricing, features, and fixes, in clear, practical, hands-on guides tested by daily use.

View all posts →