npm install effect still installs Effect v3 today. Check the package’s own dist-tags and you’ll see three live channels — latest at 3.22.2, beta past its hundredth build, rc past its hundredth build too — which means Effect v4 has been shipping breaking changes in public for months without ever becoming the thing a plain npm install gives you. That’s about to stop being true: the Effect team put v4 into Release Candidate on August 12, 2026, called it “a statement of confidence” with “no more broad breaking changes planned,” and is targeting a stable release in Q3/Q4 2026. When that flip happens, every AI coding agent whose training data treats [email protected] as “how Effect works” will keep writing Context.Tag and FiberRef into a codebase where neither exists anymore, and nothing will stop it from compiling — until it doesn’t. We pulled the actual breaking-change list from the effect-smol migration guide the Effect team maintains, not a secondary summary, because at least one popular writeup already gets a key rename wrong (more on that below).
Browse more real-world CLAUDE.md and AGENTS.md examples in our gallery.
What “RC” Actually Means Here, and Why the Timing Matters
Effect v4 has been in public beta since February 18, 2026. The RC announcement on August 12 was explicit that the API surface is now “presumed final,” with only “narrowly scoped” breaking changes possible before stable. The August RC recap adds a concrete data point: the core effect package now has zero external dependencies — even the Postgres driver was rewritten as a native protocol client (PgProtocol in @effect/sql-pg) to drop the pg package entirely.
None of that shows up if you just run npm view effect:
$ npm view effect dist-tags
{
latest: '3.22.2',
beta: '4.0.0-beta.107',
rc: '4.0.0-rc.115',
snapshot: '0.0.0-snapshot-...'
}
latest is still v3. To run v4 today you install it explicitly — npm install effect@rc — which means an agent that just runs npm install effect for a new project, or that inherits a package.json with "effect": "^3.9.0", is working against v3 by default regardless of what the docs it was trained on now say. The gap between “what ships by default” and “what the docs and blog posts already describe” is exactly the situation a CLAUDE.md needs to close, because the agent can’t infer it from the installed version number alone — it has to be told which API generation the project is actually targeting.
The Service API Collapse: Four Constructors Become One
In v3, you could define a service with Context.Tag, Context.GenericTag, Effect.Tag, or Effect.Service — four different entry points that a training corpus full of v3-era tutorials uses close to interchangeably. Per the official services migration guide, all four collapse into a single Context.Service in v4, and the argument order changes along with the name:
// v3 — Context.GenericTag
import { Context } from "effect"
interface Database {
readonly query: (sql: string) => string
}
const Database = Context.GenericTag<Database>("Database")
// v4 — Context.Service (function syntax)
import { Context } from "effect"
interface Database {
readonly query: (sql: string) => string
}
const Database = Context.Service<Database>("Database")
The class-based form changes shape more sharply — the identifier string moves from the front of the call to the end:
// v3 — Context.Tag class syntax
class Database extends Context.Tag("Database")<Database, {
readonly query: (sql: string) => string
}>() {}
// v4 — Context.Service class syntax
class Database extends Context.Service<Database, {
readonly query: (sql: string) => string
}>()("Database") {}
An agent pattern-matching on “which one of the four v3 constructors have I seen most” will reach for Context.Tag or Effect.Service out of habit. Both compile-error immediately in v4 — this isn’t a silent runtime bug, it’s a type error at the call site — but the agent still burns a turn producing code it has to walk back, and on a large refactor it can produce the same wrong pattern a dozen times before the pattern is corrected once.
Static Accessors Are Gone — And the Reason Is a Real Type Bug
This is the change most likely to survive a naive migration and fail somewhere unexpected. v3’s Effect.Tag let you call service methods as static properties directly on the tag class, without first yield*-ing the service:
// v3 — static accessor proxy
class Notifications extends Effect.Tag("Notifications")<Notifications, {
readonly notify: (message: string) => Effect.Effect<void>
}>() {}
const program = Notifications.notify("hello")
The migration guide is specific about why this was removed, not just that it was: the proxy was implemented via mapped types over the service shape, which meant generic methods lost their type parameters. A method typed get<T>(key: string): Effect<T> collapsed to get(key: string): Effect<unknown> the moment it was accessed through the static accessor — the same reason overloaded signatures silently stopped resolving correctly. In v4, accessors don’t exist, full stop. The replacement is Service.use:
// v4 — Service.use
import { Context, Effect } from "effect"
const program = Notifications.use((notifications) =>
notifications.notify("hello")
)
If your codebase leaned on the v3 accessor pattern for generic service methods, it wasn’t just convenient — it was quietly wrong the whole time it type-checked. That’s worth a line in CLAUDE.md on its own: don’t let an agent “restore” the accessor pattern to make a v4 migration look smaller.
FiberRef No Longer Exists
FiberRef, FiberRefs, FiberRefsPatch, and Differ are removed outright in v4 — not renamed, removed — and replaced by Context.Reference, the same mechanism v4 uses for services with default values. The built-in references get a straight rename, per the FiberRef migration guide:
v3 FiberRef | v4 Reference |
|---|---|
FiberRef.currentConcurrency | References.CurrentConcurrency |
FiberRef.currentLogLevel | References.CurrentLogLevel |
FiberRef.currentMinimumLogLevel | References.MinimumLogLevel |
FiberRef.currentLogAnnotations | References.CurrentLogAnnotations |
FiberRef.currentScheduler | References.Scheduler |
FiberRef.currentMaxOpsBeforeYield | References.MaxOpsBeforeYield |
FiberRef.currentTracerEnabled | References.TracerEnabled |
Reading one is now a direct yield*, since references are services:
// v3
const program = Effect.gen(function*() {
const level = yield* FiberRef.get(FiberRef.currentLogLevel)
})
// v4
const program = Effect.gen(function*() {
const level = yield* References.CurrentLogLevel
})
And Effect.locally — v3’s way of scoping a FiberRef override to a block — is replaced by Effect.provideService against the reference. Runtime<R> disappears in the same cleanup; if your agent generates a function signature typed Runtime<SomeService>, that’s a v3 tell by itself.
Schema: Where a Popular Migration Writeup Gets It Wrong
This is the section worth reading even if you skip everything else, because it’s where secondary sources — including at least one AI-agent skill file circulating for Effect v4 — actively mislead. Several summaries claim Schema.nonNegative() was renamed to Schema.isGreaterThanOrEqualTo(0). The official schema migration table says otherwise, explicitly: positive, negative, nonNegative, and nonPositive are listed under removed, not renamed, in a separate line from the filter-rename table. isGreaterThanOrEqualTo(0) is a reasonable manual replacement, but there’s no automated or guaranteed-equivalent mapping — an agent that “corrects” nonNegative() to isGreaterThanOrEqualTo(0) and calls it a straight rename is repeating a mistake that’s already loose in the ecosystem, not catching one.
The filters that did survive all picked up an is prefix:
greaterThan → isGreaterThan, lessThan → isLessThan, between → isBetween, int → isInt, minLength → isMinLength, maxLength → isMaxLength
And Schema.Data — a common wrapper for giving decoded values value-equality — has no v4 equivalent at all, because Equal.equals performs deep structural comparison on plain objects by default in v4. If an agent hits a missing Schema.Data export and reaches for a workaround instead of just dropping the wrapper, that’s a sign it’s patching around a v3 assumption that no longer applies rather than checking whether the problem exists in v4 in the first place.
Package Consolidation and effect/unstable/*
v4 also restructures how the ecosystem ships. @effect/platform, @effect/rpc, and @effect/cluster are folded into the core effect package instead of being installed separately, and all Effect packages now share one version number — [email protected] requires @effect/[email protected], not whatever version happened to resolve independently. New functionality that hasn’t stabilized yet — ai, cli, cluster, sql, workflow, and more — lives under effect/unstable/* import paths and can break in minor releases even after v4 goes stable; only the top-level effect/* namespace gets strict semver. An agent importing from effect/unstable/ai without knowing that distinction will treat a minor version bump as safe when it isn’t.
CLAUDE.md / AGENTS.md Rule to Add
## Effect Version
- This project targets Effect v4 (check `package.json` for the exact `effect` version — v4 is `4.0.0-beta.x` or `4.0.0-rc.x`, v3 is `3.x`). A plain `npm install effect` installs v3; v4 requires `effect@beta` or `effect@rc` explicitly.
- Define services with `Context.Service`, never `Context.Tag`, `Context.GenericTag`, `Effect.Tag`, or `Effect.Service` — all four are v3-only and don't exist in v4.
- Do not use static accessor methods on a service class (e.g. `MyService.doThing(...)`). Yield the service and call the method, or use `Service.use((impl) => ...)`.
- `FiberRef` does not exist in v4. Use `Context.Reference` and the `References.*` module for built-ins (`References.CurrentLogLevel`, etc.).
- Before "fixing" a Schema API that seems missing (e.g. `Schema.nonNegative`, `Schema.Data`), check the official v3-to-v4 migration guide (`effect-smol/migration/schema.md`) for whether it was renamed or removed outright — do not assume every missing v3 API has a same-shape v4 replacement.
- New/experimental modules (`ai`, `cli`, `cluster`, `sql`, `workflow`) live under `effect/unstable/*` and can break in minor versions even post-stable. Don't import from `unstable/*` and treat it as semver-stable.
Checking What Version an Agent Is Actually Working Against
Don’t rely on an agent inferring the Effect generation from code style alone — v3 and v4 patterns can coexist in a half-migrated codebase. Have it check directly:
npm ls effect --depth=0
# or, to see what channel a fresh install would pick up
npm view effect dist-tags
If package.json pins "effect": "^3.x" or omits a version constraint that would resolve to latest, the project is on v3 regardless of what any individual file’s syntax looks like — and any v4-shaped code already in the tree is either aspirational or already broken.
None of these renames are edge cases nobody hits — Context.Tag, FiberRef, and Schema.nonNegative are exactly the APIs a v3-trained model reaches for first, because they’re the most-documented, most-tutorialized entry points into the library. The RC is explicitly the signal that this API surface is close to final, not a moving target anymore, which makes now — not after stable ships and every default install flips — the right time to put the v3/v4 boundary in writing.
Browse working CLAUDE.md and AGENTS.md examples with framework- and library-specific sections from real projects in our gallery.
FAQ
Does npm install effect install v4 yet?
No. As of this writing, npm’s latest dist-tag for effect still points to a 3.x release. Effect v4 is only available via npm install effect@beta or npm install effect@rc. It becomes the default once the team ships a stable v4 release, which they’re targeting for Q3/Q4 2026.
Is Schema.nonNegative() renamed to Schema.isGreaterThanOrEqualTo(0) in v4?
No — this is a common but incorrect claim. The official migration guide lists nonNegative (along with positive, negative, and nonPositive) as removed, not renamed. isGreaterThanOrEqualTo(0) is a reasonable manual substitute, not a guaranteed drop-in equivalent.
Will code using Context.Tag or FiberRef fail silently in v4, or throw a clear error?
Neither in the way you’d expect — those APIs don’t exist in v4 at all, so code referencing them fails to compile under TypeScript, or throws an immediate undefined is not a function-style error at runtime in plain JS. It’s a hard failure, not a silent behavior change, which is actually the easier case to catch in review.
Can a v3 and v4 codebase coexist during migration?
Not within a single effect dependency — v4 renames and removes core exports v3 code relies on. What can coexist is a monorepo with separate packages pinned to different major versions while you migrate package by package, since v4’s unified versioning only requires matching versions across effect and its own @effect/* companions, not across unrelated packages in the same repo.
Where should an agent look instead of guessing at a rename?
The effect-smol repository’s MIGRATION.md and its linked per-topic guides (services.md, schema.md, fiberref.md, etc.) are the source the Effect team maintains directly. They’re more current and more precise than most third-party migration writeups, several of which (see the Schema section above) already contain small factual errors.