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.

packages/database/src/postgres-migrations.ts · 65e9465f
              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:

packages/database/src/postgres-migrations.ts · 65e9465f
                (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

OperationOwner
acquireUseReleaseOne use callback; release follows that callback’s exit
acquireReleaseThe current Scope; release runs when that Scope closes
scopedA 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.

packages/core/test/scoped-resource.test.ts · 4019cffb
      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 failure36, and interruption. Each case must acquire once and release once. It also checks that the selected exit actually occurred.

packages/core/test/scoped-resource.test.ts · 4019cffb
      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

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

Start with Layer composition13 when choosing the resource’s owner. Then read concurrent work7 before starting child work. Return to the Effect reading order17.

Sources

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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-07 in 989f258. Source on GitHub. Change log.

Linked from

  1. 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.

  2. 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.

  3. 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.

  4. Change log. What changed in the files served here, newest first.

    Scopes release resources when work ends — edited: fix(lore): re-pin the CLI failure excerpt and link glossary first uses

  5. 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.¶

  6. 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.

  7. 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.

  8. 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.

  9. 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.¶

  10. Full agent guide. An Effect stack so pure (aspirational) Kit Langton will blush.
  11. Lifecycles are machines. XState owns finite lifecycle states while declared Effect actors own side effects.

    A scope owns cleanup when that work ends.¶

  12. Lint and type escape ledger (debt.md). A source-linked count of repo-owned lint and type escapes.
  13. Oxlint rule limits. How the current lint rules draw their syntax boundaries.
  14. 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.

  15. 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.

  16. 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.

  17. 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.

  18. ship. Learn how to ship a change through merge, CI, release, stage deployment, rollout, verification, rollback and flags.
  19. Source change history (log.md). The generated change log as Markdown.

    Scopes release resources when work ends — edited: fix(lore): re-pin the CLI failure excerpt and link glossary first uses

  20. 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.

Unlinked mentions (6)

Content updated .