How Magnitude Manages Error Handling and Resource Management with Effect Monads

Magnitude leverages Effect-TS to implement typed error handling and deterministic resource management through monadic constructs like Effect.acquireRelease, Effect.catchAll, and Effect.interruptible, ensuring that database connections, daemon processes, and background workers are safely acquired, used, and cleaned up even during failures or cancellations.

Magnitude is an open-source testing framework that builds its core runtime on Effect-TS, a functional effects library that provides monadic abstractions for asynchronous work. By encoding side effects and error conditions as typed values within the Effect monad, the codebase achieves deterministic resource cleanup and explicit error propagation across package boundaries.

Typed Error Handling with Effect Monads

Declaring Domain-Specific Error Types

Magnitude defines domain-specific errors as subclasses of Effect.Exception, which carry a tag that can be pattern-matched downstream. In packages/acn-protocol/src/errors/AgentError.ts, the base error types for the ACN daemon extend the Effect exception hierarchy, allowing typed error propagation throughout the stack.

These typed errors are raised using Effect.fail and transformed using Effect.mapError. For example, when the VCS layer encounters a filesystem error, it converts the raw exception into a domain-specific VcsError:

// packages/vcs/src/backend/Backend.ts
import * as Effect from "effect";
import { VcsError } from "../errors/VcsError";

export const readFileAt = (hash: string, path: string) =>
  Effect.tryPromise(() => backend.readFile(hash, path))
    .mapError((e) => new VcsError(`Failed to read ${path}: ${e.message}`));

This pattern ensures that low-level implementation details do not leak across package boundaries, while preserving the error chain for debugging.

Catching and Transforming Errors

At call sites, Effect.catchAll and Effect.flatMapError wrap operations to transform or log errors before re-throwing. In packages/sdk/src/ProviderClient.ts, cross-boundary error translation converts internal exceptions into higher-level API errors suitable for client consumption:

  • Effect.catchAll: Catches all errors and provides a fallback effect
  • Effect.mapError: Transforms the error type without changing the success type
  • Effect.tapError: Logs errors with contextual data before re-throwing

Resource Management and Safe Acquisition

Effect.acquireRelease for Database Connections

Long-lived resources such as SQLite handles, file descriptors, and the ACN daemon process are created inside Effect.acquireRelease blocks. This construct guarantees that the release step executes exactly once on success, failure, or interruption.

In packages/acn/src/boundary/Database.ts, the database driver implementation demonstrates this pattern:

// desktop/src/sqlite-driver.ts
import * as Effect from "effect";
import { Database } from "better-sqlite3";

export const makeDatabase = (path: string) =>
  Effect.acquireRelease(
    Effect.try(() => new Database(path, { verbose: console.log })),
    (db) => Effect.succeed(() => db.close())
  );

The acquire effect creates the database connection, while the release effect closes it. The Effect runtime ensures db.close() runs even if the surrounding computation is interrupted.

Scoped Resource Lifecycles

For complex resource trees, Magnitude uses Effect.scoped to delimit the lifetime of acquired resources. When the scope ends—either normally or exceptionally—the runtime invokes all registered finalizers in reverse order of acquisition. This prevents resource leaks in the Agent scheduler and VCS watcher components.

Concurrency and Interruptible Work

Forking Background Workers

Background processes like the Agent scheduler and VCS watcher are spawned using Effect.fork inside Effect.interruptible regions. This allows graceful cancellation when the client disconnects or the application shuts down.

In packages/agent/src/scheduler/AgentScheduler.ts, the scheduler runs as a separate fiber:

// packages/agent/src/scheduler/AgentScheduler.ts
import * as Effect from "effect";

export const startScheduler = Effect.interruptible(
  Effect.fork(
    Effect.repeatEffect(
      Effect.sleep(1000).andThen(pollWorkQueue()),
      { schedule: Effect.Schedule.spaced(1000) }
    )
  )
);

The interruptible wrapper ensures that a single cancellation signal stops the entire daemon cleanly, releasing all pending resources held by the forked fiber.

Cancellation Semantics

Effect monads preserve cancellation semantics when lifting promises via Effect.tryPromise or Effect.fromPromise. If a parent fiber is interrupted, child fibers receive the cancellation signal, and all acquireRelease finalizers execute before the effect completes.

Compositional Error Handling Patterns

Sequential Composition with Effect.gen

Higher-level operations compose lower-level effects using Effect.flatMap or the more readable Effect.gen syntax. In packages/agent/src/AgentClient.ts, task execution chains multiple effects while centralizing error handling:

// packages/agent/src/AgentClient.ts
import * as Effect from "effect";
import { AgentError } from "../errors/AgentError";

export const runTask = (taskId: string) =>
  Effect.gen(function* (_) {
    const task = yield* _(fetchTask(taskId));
    const result = yield* _(execute(task));
    return result;
  }).catchAll((e) => Effect.fail(new AgentError(`Task ${taskId} failed`, e)));

The generator syntax provides sequential readability while maintaining referential transparency. Errors bubble up automatically until explicitly caught by catchAll, which wraps them in AgentError for upstream consumers.

Cross-Boundary Error Translation

When propagating errors across package boundaries (e.g., from acn to sdk), Magnitude uses mapError to convert low-level exceptions into user-friendly API errors. This transformation occurs at module boundaries, ensuring that internal implementation details do not leak into the public API surface.

Observability and Structured Logging

Effect monads integrate with Magnitude's OpenTelemetry "Motel" collector through structured logging primitives. In packages/client-common/src/logging/EffectLogger.ts, the Effect.tapError and Effect.logError functions attach contextual metadata to errors before they propagate:

  • Effect.tapError: Executes a side effect (like logging) with the error value
  • Effect.logError: Records the error with severity levels compatible with telemetry spans

This approach surfaces effect failures as trace spans in the monitoring system, making debugging easier without swallowing the original error.

Summary

  • Effect-TS provides the monadic foundation for Magnitude's error handling and resource management, offering typed guarantees that prevent unchecked exceptions.

  • Effect.acquireRelease ensures deterministic finalizers for database connections and daemon processes, executing cleanup code exactly once regardless of success, failure, or interruption.

  • Effect.fail and Effect.mapError enable domain-specific error types that propagate across package boundaries while preserving type safety and error context.

  • Effect.fork and Effect.interruptible allow concurrent background workers that remain cancellable, preventing resource leaks during shutdown sequences.

  • Effect.gen provides a readable sequential syntax for composing complex effect chains, while catchAll centralizes error transformation at appropriate abstraction layers.

Frequently Asked Questions

What are Effect monads and why does Magnitude use them?

Effect monads are functional programming constructs that represent computations as typed values, encapsulating both success and failure cases along with resource acquisition and cleanup logic. Magnitude uses Effect-TS (the TypeScript implementation) to replace untyped promises with explicit error types, ensuring that every possible failure mode is accounted for in the type system. This eliminates runtime surprises and enables deterministic resource management through constructs like acquireRelease.

How does Effect.acquireRelease prevent resource leaks?

Effect.acquireRelease takes two arguments: an acquire effect that creates a resource (like opening a database), and a release effect that cleans it up (like closing the connection). The Effect runtime guarantees the release effect executes exactly once when the scope exits—whether through successful completion, failure, or interruption. In packages/acn/src/boundary/Database.ts, this ensures SQLite handles never leak even if the Agent process crashes.

Can Effect monads handle concurrent operations and cancellation?

Yes. Effect-TS provides fiber-based concurrency through Effect.fork, which spawns independent computations, and Effect.interruptible, which marks regions as cancellable. In packages/agent/src/scheduler/AgentScheduler.ts, the background polling loop runs in a forked fiber that can be cleanly shut down via interruption signals. When interrupted, all acquireRelease finalizers in the fiber's scope run before shutdown completes.

How does Magnitude convert low-level errors into domain-specific errors?

Magnitude uses Effect.mapError and Effect.catchAll at module boundaries to transform exceptions. For example, packages/vcs/src/backend/Backend.ts uses mapError to wrap raw filesystem errors in VcsError instances, adding context about which file operation failed. This pattern ensures that consumers of the VCS package receive typed errors they can handle appropriately, rather than untyped JavaScript exceptions.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →