Scopes release resources when work ends
A Scope owns resource cleanup. A fiber is an independently running Effect. Acquisition obtains a resource; release cleans it up.¶
Construct the resource inside an acquisition Effect. Register release before handing the resource to its caller. Keep use inside the resource’s lifetime.¶
Acquire, use, and release one resource
The migration adapter opens a Postgres client inside Effect.tryPromise. acquireUseRelease gives the client to migration work and then runs shutdown.¶
return yield* Effect.acquireUseRelease(
Effect.tryPromise({
catch: (cause) =>
new PostgresMigrationConnectionError({ cause }),
// @effect-diagnostics-next-line asyncFunction:off -- The pg client exposes Promise-based connection setup.
try: async () => {
const connection = new Client({ connectionString: url });
await connection.connect();
return connection;
},
}),
(connection) =>
runPostgresMigrations(
input.directory,
makePgMigrationExecutor(connection)
),The release callback closes the connection:¶
(connection) =>
Effect.tryPromise({
catch: (cause) =>
new PostgresMigrationConnectionError({ cause }),
// @effect-diagnostics-next-line asyncFunction:off -- The pg client exposes Promise-based shutdown.
try: async () => {
await connection.end();
},
}).pipe(Effect.orDie)This example handles one acquired connection. If acquisition fails before release registration, the acquisition code must clean up any partial resource it created.¶
Choose the lifetime
acquireUseRelease: One use callback; release follows that callback’s exit¶acquireRelease: The current Scope; release runs when that Scope closes¶scoped: A new Scope around the supplied Effect¶
A resource finalizer is cleanup work attached to an owner. It is not a business-state transition.¶
Use acquireRelease when several operations need the same resource.
A service Layer can own that resource for its build lifetime.
Callers must not retain the handle after its Scope closes.¶
Prove all use exits
The checked fixture creates a counted handle inside Effect.sync.
Its finalizer increments a separate release counter.¶
const acquired = yield* Ref.make(0);
const released = yield* Ref.make(0);
const started = yield* Deferred.make<boolean>();
const acquire = Effect.acquireRelease(
Effect.sync(() => ({ name: "counted-resource" })).pipe(
Effect.tap(() => Ref.update(acquired, (count) => count + 1)),
Effect.tap(() => Deferred.succeed(started, true))
),
() => Ref.update(released, (count) => count + 1)
);The test cases exercise success, expected failure39, and interruption. Each case must acquire once and release once. It also checks that the selected exit actually occurred.¶
if (outcome === "interruption") {
const fiber = yield* Effect.forkChild(use);
yield* Deferred.await(started);
yield* Fiber.interrupt(fiber);
const exit = yield* Fiber.await(fiber);
expect(Exit.isFailure(exit) && Cause.hasInterrupts(exit.cause)).toBe(
true
);
} else {
const result = yield* Effect.result(use);
expect(Result.isFailure(result)).toBe(outcome === "failure");
if (Result.isFailure(result)) {
expect(result.failure).toBe("expected-refusal");
}
}
expect(yield* Ref.get(acquired)).toBe(1);
expect(yield* Ref.get(released)).toBe(1);Interrupting a fiber does not bypass its registered finalizer.¶
Common mistakes
- Allocate the client before building the acquisition Effect. An interruption can leave that client without a registered finalizer.¶
- Return a resource from
scopedand use it later. Its lifetime has already ended.¶ - Claim failed acquisition always invokes release. Release registration requires successful acquisition.¶
- Treat a close failure as harmless without checking the adapter’s failure policy.¶
The migration adapter makes shutdown failure a defect with orDie.
Ordinary callers still need declared failures in the typed channel.¶
Effect idiom and house rule
- A Scope owns finalizers; acquire/use/release operations arrange that sequence.¶
- In rat-stack, construct resources inside acquisition. The lint fence45 checks this shape.¶
- In rat-stack, machines own finite business lifecycles26. Resource cleanup does not replace a lifecycle machine26.¶
Start with Layer composition18 when choosing the resource’s owner. Then read concurrent work23 before starting child work.¶
A Scope ends with its process. Steady state recycles resources9 covers growth that outlives one Scope.¶
Return to the Effect reading order29.¶
Sources
- Effect contributors. 2026. Effect. Effect 4.0.0.. Effect-TS. acquireRelease registers release after successful acquisition; scoped supplies and closes a Scope; acquireUseRelease brackets one use. Accessed 2026-10-06.
- Effect contributors. 2026. Acquiring resources with Effect.acquireRelease.. Effect-TS. Construct resources inside the acquisition Effect and install their finalizers in a service Layer. Accessed 2026-10-06.
- rat-stack contributors. 2026. Postgres migration resource ownership.. rat-stack. The migration adapter constructs and connects the client inside acquisition, uses it, then closes it. Accessed 2026-10-06.
- rat-stack contributors. 2026. Scoped resource exit fixture.. rat-stack. Counts one acquisition and one release after successful use, expected failure, and fiber interruption. Accessed 2026-10-06.
- Langton, Kit. 2026. Testing. Effect Solutions.. Kit Langton. Scoped test resources supply a useful ownership question; this guide uses independent rat-stack examples and exact Effect source. Accessed 2026-10-06.
Last changed 2026-10-24 in abefe39. Source on GitHub. Change log.
Linked from
- add-a-feature-flag. Declare a typed feature flag, its scoped evidence ladder, approval roles, removal date, and durable ledger adapter.
Omission means the literal global scope.¶
- Agent guide. An Effect stack so pure (aspirational) Kit Langton will blush.
Scopes release resources when work ends — Acquire resources lazily, keep their use inside the owner, and release them after success, failure, or interruption.
- Agent rules for rat-stack (AGENTS.md). What you may change, which commands to run, and which changes need approval.
Child configs may add exact allowedUnstableApis only for modules warned about in that scope.
- Build and run rat-stack (README.md). What is in the repo, how the example works, and how to run it.
The model gets search for ranked matches with TypeScript signatures and execute for JavaScript with tools in scope.
- Bulkheads isolate scarce capacity. Reserve independent capacity for workloads that must not starve each other.
Connect this to the existing rat-stack guidance.¶
- Change log. What changed in the files served here, newest first.
Scopes release resources when work ends — edited: fix(site): replace published Markdown tables with lists and fence reader output
- Code snippets quote a pinned source. Shipped design brief: pinned Git objects, typed diagnostics, and one build-only snippet model for both views.
A Scope owns resource cleanup.¶
- Concurrent work needs an owner and a bound. Limit active work, retain result order, and stop child work when its owning lifetime ends.
The sandbox writes queued input to a child process through a scoped fiber: child work that stops when its owning Scope closes.
- durability. Keep a rat-stack app standing when a provider, pool, or dependency slows down or a worker dies. Use at any I/O boundary, connection pool, provider call, shared waiter, fan-out, flag, gate, or work that can outlive a request, or when asked "what happens when this is slow", "add a timeout", "make this durable", or "what survives a crash".
Scope waiters to the request Principle: principles/scope-waiters-to-the-request.md¶
- Durability. Seven durable Effect patterns, each with its seam, its fence, and the incident that taught it.
Primitive: a Deferred, Cache, Ref, Semaphore, Queue, Latch, PubSub, PartitionedSemaphore, or TxSemaphore created inside one request or runtime scope.¶
- Durability bibliography. Books, checked short quotations, source limits, and Effect mapping provenance.
Designing Data-Intensive Applications and Newman’s Building Microservices gave only fragments for their scope.¶
- Durability starts with a recovery record. A reading order for storage, replay, worker ownership, overload, and recovery tests.
Effect is not BEAM: Fibers share a runtime; scope ownership does not supply isolated heaps or OTP restart strategies.¶
- Effect + Alchemy peer patterns. Source-grounded patterns from repos on nearby Effect and Alchemy pins.
Service turns it into module- scope query and mutation atoms for React.
- Effect 4 study: September 2026. Dated Effect 4 source studies from September 2026; current versions live in pins.md.
org/@xstate%2feffect, a first-publish scope problem.
- Effect basics. An Effect describes work, expected failures, and required services before a runtime executes it.
Scopes: acquire resources inside their owner and release them on exit.¶
- Effect is not BEAM. Fibers share a runtime; scope ownership does not supply isolated heaps or OTP restart strategies.
Scope and FiberSet own local lifetimes.
- Every task needs an owner. Give child work a lifetime, a failure observer, and a recovery decision.
forkScoped and FiberSet attach work to a Scope.
- Feature flags. Typed feature flags with owners, removal dates, and stable subject rollouts.
record_flag_evidence records a measurement at the current rung and exact scope.¶
- Flags climb on evidence. A flag ladder connects each rollout step to scoped measurements, approval, and a durable receipt.
The child’s readScope schema defines the flag’s scope.
- Full agent guide. An Effect stack so pure (aspirational) Kit Langton will blush.
- Lifecycles are machines. XState owns finite lifecycle states while declared Effect actors own side effects.
A scope owns cleanup when that work ends.¶
- Lint and type escape ledger (debt.md). A source-linked count of repo-owned lint and type escapes.
Reason: The SDK-erased registered exporter is rebuilt by WorkerBridge inside each event scope.
- Oxlint rule limits. How the current lint rules draw their syntax boundaries.
Link context
no-browser-globals-on-server: Covered now: Bare runtime references, literal/template member keys, globalThis/self/global/window, unchanged aliases, and destructuring are checked with value- scope awareness.
- Purpose and boundaries of rat-stack (VISION.md). What this starter is for and what a useful copy should keep.
Scope: joelhooks/rat-stack, the reference repo, and the shape a project inherits when it vendors or clones it.
- Rat Stack lore | rat-stack. Short, source-grounded notes on the ideas and decisions behind rat-stack.
Scopes release resources when work ends — Acquire resources lazily, keep their use inside the owner, and release them after success, failure, or interruption.
- rat-stack-mode. Route non-trivial rat-stack code, data, design, investigation, and documentation tasks to every matching playbook and principle.
md sets the intent, reliability goals, scope, package-removal test, and shrinking debt.
- Retry load needs a budget. One retry owner must control attempts, delay, and aggregate load.
Shared budgets require an explicit owner and accounting scope.¶
- Run Effect at the integration boundary. Build the runtime where a calling framework enters, reuse its services, and dispose it when that integration ends.
Read scopes when deciding what the integration owns.
- ship. Learn how to ship a change through merge, CI, release, stage deployment, rollout, verification, rollback and flags.
WAIT[release- scope]: Authorization and the target stage are known.¶
- Source change history (log.md). The generated change log as Markdown.
Scopes release resources when work ends — edited: fix(site): replace published Markdown tables with lists and fence reader output
- Steady state recycles resources. Every accumulation needs a deliberate recycling policy.
Cache and Scope cover runtime lifetime.
- Supervision chooses what restarts. Restart policy follows dependency shape and bounds repeated recovery.
Scope and FiberSet handle lifetime.
- Tests prove behavior through supplied services. Supply services through Layers, choose properties or models for domain rules, and reject tests that cannot detect broken behavior.
A Scope owns cleanup.
- uncomplect. Find responsibilities that a rat-stack design combines, separate them into capabilities, cartridges, machines, features, and clients, and enforce that separation through types and checks. Use when asked to simplify, review, or replace a design, "what would Rich Hickey do", "optimize for deletion", or "define this error out of existence".
A held connection, a request, and a provider call in one scope is a braid.
Unlinked mentions (12)
- add-a-feature-flag → Each climb needs evidence from the current rung and its exact read scope.¶
- Agent rules for rat-stack (AGENTS.md) → Read, edit, and test inside your write scope.
- Build and run rat-stack (README.md) → A new scope changes line lengths, so run the formatter once after the rename:
- Concurrent work needs an owner and a bound → A Scope owns cleanup.¶
- durability → uncomplect: a held resource and a provider call in one scope are a braid to separate.¶
- Effect + Alchemy peer patterns → Scope clients to one invocation.
- Effect 4 study: September 2026 → sleep on the Effect Clock so TestClock drives after, declared Effect actions fork in the actor scope, and R is collected from declared actions and actors via RequirementsFrom.
- Feature flags → The default operator adapter uses one Durable Object per flag and scope.
- Flags climb on evidence → A read scope is the partition for a flag value and its evidence.
- Lifecycles are machines → createEffectActor starts the machine inside Effect’s scope.¶
- Oxlint rule limits → Covered now: Module-scope let/var and nested module-scoped var are reported; immutable using and await using remain allowed.
- ship → Do not expand the approved scope to credentials, infrastructure, data formats, provider writes, or messages to real recipients.¶
Content updated .