effect-patterns-error-handling

A set of Effect-TS patterns for handling errors, including collecting several validation problems instead of stopping at the first one. Effect-TS is a TypeScript library for describing and running programs with explicit errors and other effects.

In plain words
What is it for?
Use it when adding or reviewing error handling in Effect-TS applications, especially validation and batch-processing code.
Why use it?
It helps avoid inconsistent error-handling code and gives users a complete list of problems when validating forms or processing batches.

Skill for Claude CodeCodex

Install

Getting it into your agent

One page per mod, every tool's command on it. A separate URL per tool would split the same page into five that compete with each other.

agentmods
npx agentmods add skills/pauljphilp/effectpatterns/effect-patterns-error-handling
Any agent
npx skills add PaulJPhilp/EffectPatterns --skill effect-patterns-error-handling
Clone the repo
git clone --depth 1 https://github.com/PaulJPhilp/EffectPatterns

Made for: Claude Code, Codex.

Per session 27 Skills are progressive disclosure: only the name and description are preloaded; the body loads when the skill is used.
When invoked 7,156 The whole file, excluding the scripts and references it only reads on demand.
Security scan A 0 findings. Scan, not verified.
Origin original No closer match found in the catalogue.
Token cost

What it costs to keep this loaded

Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.

ModelPer sessionOnce invoked
Fable 5 $0.00027 $0.07156
Opus 5 $0.00014 $0.03578
Sonnet 5 $0.00005 $0.01431
Haiku 4.5 $0.00003 $0.00716

Measured 2d ago against content hash 42ec479ab3b7, method: parsed. Prices are Anthropic first-party input rates as of 2026-08-30, from the pricing page.

Security

Grade A, and why

effect-patterns-error-handling scanned grade A with 0 findings against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 2d ago.

A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.

Nothing flagged

None of the 26 patterns this scan looks for appear in this file: no shell pipes, no recursive deletes, no credential paths, no hidden text, no instruction-override or anti-refusal phrasing, no agent-config snooping. That is not a guarantee, it is the absence of the things that are checkable.

config/.claude-plugin/plugins/effect-patterns/skills/effect-patterns-error-handling/SKILL.md · 1,213 lines

How it starts

The opening of the file, as written. The whole thing — 1,213 lines — stays where its author put it; the contents beside it link to each section on GitHub.

Effect-TS Patterns: Error Handling

This skill provides 3 curated Effect-TS patterns for error handling. Use this skill when working on tasks related to:

  • error handling
  • Best practices in Effect-TS applications
  • Real-world patterns and solutions

🟡 Intermediate Patterns

Error Handling Pattern 1: Accumulating Multiple Errors

Rule: Use error accumulation to report all problems at once rather than failing early, critical for validation and batch operations.

Good Example:

This example demonstrates error accumulation patterns.

import { Effect, Data, Cause } from "effect";

interface ValidationError {
  field: string;
  message: string;
  value?: unknown;
}

interface ProcessingResult<T> {
  successes: T[];
  errors: ValidationError[];
}

// Example 1: Form validation with error accumulation
const program = Effect.gen(function* () {
  console.log(`\n[ERROR ACCUMULATION] Collecting multiple errors\n`);

  // Form data
  interface FormData {
    name: string;
    email: string;
    age: number;
    phone: string;
  }

  const validateForm = (data: FormData): ValidationError[] => {
    const errors: ValidationError[] = [];

    // Validation 1: Name
    if (!data.name || data.name.trim().length === 0) {
      errors.push({
        field: "name",
        message: "Name is required",
        value: data.name,
      });
    } else if (data.name.length < 2) {
      errors.push({
        field: "name",
        message: "Name must be at least 2 characters",
        value: data.name,
      });
    }

    // Validation 2: Email
    if (!data.email) {
      errors.push({
        field: "email",
        message: "Email is required",
        value: data.email,
      });
    } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
      errors.push({
        field: "email",
        message: "Email format invalid",
        value: data.email,
      });
    }

    // Validation 3: Age
    if (data.age < 0 || data.age > 150) {
      errors.push({
        field: "age",
        message: "Age must be between 0 and 150",
        value: data.age,
      });
    }

    // Validation 4: Phone
    if (data.phone && !/^\d{3}-\d{3}-\d{4}$/.test(data.phone)) {
      errors.push({
        field: "phone",
        message: "Phone must be in format XXX-XXX-XXXX",
        value: data.phone,
      });
    }

    return errors;
  };

  // Example 1: Form with multiple errors
  console.log(`[1] Form validation with multiple errors:\n`);

  const invalidForm: FormData = {
    name: "",
    email: "not-an-email",
    age: 200,
    phone: "invalid",
  };

  const validationErrors = validateForm(invalidForm);

  yield* Effect.log(`[VALIDATION] Found ${validationErrors.length} errors:\n`);

  for (const error of validationErrors) {
    yield* Effect.log(`  ✗ ${error.field}: ${error.message}`);
  }

  // Example 2: Batch processing with partial success
  console.log(`\n[2] Batch processing (accumulate successes and failures):\n`);

  interface Record {
    id: string;
    data: string;
  }

  const processRecord = (record: Record): Result<string> => {
    if (record.id.length === 0) {
      return { success: false, error: "Missing ID" };
    }

    if (record.data.includes("ERROR")) {
      return { success: false, error: "Invalid data" };
    }

    return { success: true, value: `processed-${record.id}` };
  };

  interface Result<T> {
    success: boolean;
    value?: T;
    error?: string;
  }

  const records: Record[] = [
    { id: "rec1", data: "ok" },
    { id: "", data: "ok" }, // Error: missing ID
    { id: "rec3", data: "ok" },
    { id: "rec4", data: "ERROR" }, // Error: invalid data
    { id: "rec5", data: "ok" },
  ];

  const results: ProcessingResult<string> = {
    successes: [],
    errors: [],
  };

  for (const record of records) {
    const result = processRecord(record);

    if (result.success) {
      results.successes.push(result.value!);
    } else {
      results.errors.push({
        field: record.id || "unknown",
        message: result.error!,
      });
    }
  }

  yield* Effect.log(
    `[BATCH] Processed ${records.length} records`
  );
  yield* Effect.log(`[BATCH] ✓ ${results.successes.length} succeeded`);
  yield* Effect.log(`[BATCH] ✗ ${results.errors.length} failed\n`);

  for (const success of results.successes) {
    yield* Effect.log(`  ✓ ${success}`);
  }

  for (const error of results.errors) {
    yield* Effect.log(`  ✗ [${error.field}] ${error.message}`);
  }

  // Example 3: Multi-step validation with error accumulation
  console.log(`\n[3] Multi-step validation (all checks run):\n`);

  interface ServiceHealth {
    diskSpace: boolean;
    memory: boolean;
    network: boolean;
    database: boolean;
  }

  const diagnostics: ValidationError[] = [];

  // Check 1: Disk space
  const diskFree = 50; // MB

  if (diskFree < 100) {
    diagnostics.push({
      field: "disk-space",
      message: `Only ${diskFree}MB free (need 100MB)`,
      value: diskFree,
    });
  }

  // Check 2: Memory
  const memUsage = 95; // percent

  if (memUsage > 85) {
    diagnostics.push({
      field: "memory",
      message: `Using ${memUsage}% (threshold: 85%)`,
      value: memUsage,
    });
  }

  // Check 3: Network
  const latency = 500; // ms

  if (latency > 200) {
    diagnostics.push({
      field: "network",
      message: `Latency ${latency}ms (threshold: 200ms)`,
      value: latency,
    });
  }

  // Check 4: Database
  const dbConnections = 95;
  const dbMax = 100;

  if (dbConnections > dbMax * 0.8) {
    diagnostics.push({
      field: "database",
      message: `${dbConnections}/${dbMax} connections (80% threshold)`,
      value: dbConnections,
    });
  }

  if (diagnostics.length === 0) {
    yield* Effect.log(`[HEALTH] ✓ All systems normal\n`);
  } else {
    yield* Effect.log(
      `[HEALTH] ✗ ${diagnostics.length} issue(s) detected:\n`
    );

    for (const diag of diagnostics) {
      yield* Effect.log(`  ⚠ ${diag.field}: ${diag.message}`);
    }
  }

  // Example 4: Error collection with retry decisions
  console.log(`\n[4] Error collection for retry strategy:\n`);

  interface ErrorWithContext {
    operation: string;
    error: string;
    retryable: boolean;
    timestamp: Date;
  }

  const operationErrors: ErrorWithContext[] = [];

  const operations = [
    { name: "fetch-config", fail: false },
    { name: "connect-db", fail: true },
    { name: "load-cache", fail: true },
    { name: "start-server", fail: false },
  ];

  for (const op of operations) {
    if (op.fail) {
      operationErrors.push({
        operation: op.name,
        error: "Operation failed",
        retryable: op.name !== "fetch-config",
        timestamp: new Date(),
      });
    }
  }

  yield* Effect.log(`[OPERATIONS] ${operationErrors.length} errors:\n`);

  for (const err of operationErrors) {
    const status = err.retryable ? "🔄 retryable" : "❌ non-retryable";
    yield* Effect.log(`  ${status}: ${err.operation}`);
  }

  if (operationErrors.every((e) => e.retryable)) {
    yield* Effect.log(`\n[DECISION] All errors retryable, will retry\n`);
  } else {
    yield* Effect.log(`\n[DECISION] Some non-retryable errors, manual intervention needed\n`);
  }
});

Effect.runPromise(program);

Read the full file on GitHub · 1,213 lines

Changes

What this file has done since we first saw it

Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.

  1. 2d ago First seen · 1,213 lines · 27 tokens per session scan A 42ec479ab3b7

Subscribe to this mod's changes

effect-patterns-error-handling is a skill published in the GitHub repository PaulJPhilp/EffectPatterns (795 stars, last pushed 2mo ago), licensed MIT. It adds 27 tokens to every session and 7,156 once invoked, about $0.0001 per session on Opus 5. A static security scan graded it A with 0 findings. No closer match exists in the catalogue, so it is treated as the original; first seen 2026-08-30.

Related

Other skills, from other repositories

effect-best-practices

Enforces Effect-TS patterns for services, errors, layers, and atoms. Use when writing code with Effect.Service, Schema.TaggedError, Layer composition, or effect-atom React components.

betalyra/effect-skills · 45 tokens

effect-http-api

Build typed HTTP APIs with Effect's HttpApi — endpoints with schemas, handlers, security middleware, OpenAPI docs, derived clients, and handler unit tests. Use when building HTTP servers, REST APIs, or typed HTTP clients with Effect v4.

mpsuesser/pi-effect-harness · 53 tokens

effect-error-handling

Implement typed error handling in Effect v4 using Schema.TaggedErrorClass, catchTag/catchTags, catchReason/catchReasons, Cause, ErrorReporter, and recovery patterns. Use this skill when working with Effect error channels, handling expected failures, or designing error recovery strategies.

mpsuesser/pi-effect-harness · 61 tokens

effect-http-server

Build HTTP servers with effect/unstable/http — HttpRouter routes and middleware, HttpServerRequest schema decoding, HttpServerResponse constructors, multipart uploads, websocket upgrades, static files, NodeHttpServer/BunHttpServer layers, and in-memory web handlers. Use when serving raw HTTP routes, reading request…

mpsuesser/pi-effect-harness · 84 tokens

effect-fiber

Fork, supervise, and interrupt Effect fibers with Effect.forkChild/forkScoped/forkIn/forkDetach, Fiber join/await/interrupt, uninterruptible regions, and the FiberHandle/FiberMap/FiberSet supervision collections. Use when running background work, cancelling or restarting tasks, implementing latest-wins or keyed…

mpsuesser/pi-effect-harness · 85 tokens

effect-http-client

Make outgoing HTTP requests with Effect's HttpClient — HttpClientRequest builders, schema-decoded HttpClientResponse bodies, the HttpClientError taxonomy, retryTransient/rate limiting/cookies/redirects, streaming uploads and downloads, and FetchHttpClient/NodeHttpClient transport layers. Use when calling external…

mpsuesser/pi-effect-harness · 92 tokens