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.
npx agentmods add rules/pauljphilp/effectpatterns/error-handling-pattern-3-custom-error-strategiesgit clone --depth 1 https://github.com/PaulJPhilp/EffectPatternsWrote this? Show the measurements
A badge with what this costs and how it scanned, read live from this page, so it follows the numbers instead of freezing them. Markdown for a README, HTML for a documentation site or a project page.
[](https://agentmods.dev/rules/pauljphilp/effectpatterns/error-handling-pattern-3-custom-error-strategies)<a href="https://agentmods.dev/rules/pauljphilp/effectpatterns/error-handling-pattern-3-custom-error-strategies"><img src="https://agentmods.dev/badge/rules/pauljphilp/effectpatterns/error-handling-pattern-3-custom-error-strategies.svg" alt="Measured on agentmods" height="20"></a>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.
| Model | Per session | Once invoked |
|---|---|---|
| Fable 5.1 | $0.02660 | $0.02660 |
| Opus 5 | $0.01330 | $0.01330 |
| Sonnet 5 | $0.00532 | $0.00532 |
| Haiku 4.5 | $0.00266 | $0.00266 |
Grade A, and why
error-handling-pattern-3-custom-error-strategies 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 3d 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.
How it starts
The opening of the file, as written. The whole thing — 461 lines — stays where its author put it; the contents beside it link to each section on GitHub.
description: Use tagged errors and custom error types to enable type-safe error handling and business-logic-aware recovery strategies. globs: "**/*.ts" alwaysApply: true
Error Handling Pattern 3: Custom Error Strategies
Rule: Use tagged errors and custom error types to enable type-safe error handling and business-logic-aware recovery strategies.
Example
This example demonstrates custom error strategies.
import { Effect, Data, Schedule } from "effect";
// Custom domain errors
class NetworkError extends Data.TaggedError("NetworkError")<{
endpoint: string;
statusCode?: number;
retryable: boolean;
}> {}
class ValidationError extends Data.TaggedError("ValidationError")<{
field: string;
reason: string;
}> {}
class AuthenticationError extends Data.TaggedError("AuthenticationError")<{
reason: "invalid-token" | "expired-token" | "missing-token";
}> {}
class PermissionError extends Data.TaggedError("PermissionError")<{
resource: string;
action: string;
}> {}
class RateLimitError extends Data.TaggedError("RateLimitError")<{
retryAfter: number; // milliseconds
}> {}
class NotFoundError extends Data.TaggedError("NotFoundError")<{
resource: string;
id: string;
}> {}
// Recovery strategy selector
const selectRecoveryStrategy = (
error: Error
): "retry" | "fallback" | "fail" | "user-message" => {
if (error instanceof NetworkError && error.retryable) {
return "retry";
}
if (error instanceof RateLimitError) {
return "retry"; // With backoff
}
if (error instanceof ValidationError) {
return "user-message"; // User can fix
}
if (error instanceof NotFoundError) {
return "fallback"; // Use empty result
}
if (
error instanceof AuthenticationError &&
error.reason === "expired-token"
) {
return "retry"; // Refresh token
}
if (error instanceof PermissionError) {
return "fail"; // Don't retry
}
return "fail"; // Default: don't retry
};
const program = Effect.gen(function* () {
console.log(
`\n[CUSTOM ERROR STRATEGIES] Domain-aware error handling\n`
);
// Example 1: Type-safe error handling
console.log(`[1] Type-safe error catching:\n`);
const operation1 = Effect.fail(
new ValidationError({
field: "email",
reason: "Invalid format",
})
);
const handled1 = operation1.pipe(
Effect.catchTag("ValidationError", (error) =>
Effect.gen(function* () {
yield* Effect.log(`[CAUGHT] Validation error`);
yield* Effect.log(` Field: ${error.field}`);
yield* Effect.log(` Reason: ${error.reason}\n`);
return "validation-failed";
})
)
);
yield* handled1;
// Example 2: Multiple error types with different recovery
console.log(`[2] Different recovery per error type:\n`);
interface ApiResponse {
status: number;
body?: unknown;
}
const callApi = (shouldFail: "network" | "validation" | "ratelimit" | "success") =>
Effect.gen(function* () {
switch (shouldFail) {
case "network":
yield* Effect.fail(
new NetworkError({
endpoint: "https://api.example.com/data",
statusCode: 503,
retryable: true,
})
);
case "validation":
yield* Effect.fail(
new ValidationError({
field: "id",
reason: "Must be numeric",
})
);
case "ratelimit":
yield* Effect.fail(
new RateLimitError({
retryAfter: 5000,
})
);
case "success":
return { status: 200, body: { id: 123 } };
}
});
// Test each error type
const testCases = ["network", "validation", "ratelimit", "success"] as const;
for (const testCase of testCases) {
const strategy = yield* callApi(testCase).pipe(
Effect.catchTag("NetworkError", (error) =>
Effect.gen(function* () {
yield* Effect.log(
`[NETWORK] Retryable: ${error.retryable}, Status: ${error.statusCode}`
);
return "will-retry";
})
),
Effect.catchTag("ValidationError", (error) =>
Effect.gen(function* () {
yield* Effect.log(
`[VALIDATION] ${error.field}: ${error.reason} (no retry)`
);
return "user-must-fix";
})
),
Effect.catchTag("RateLimitError", (error) =>
Effect.gen(function* () {
yield* Effect.log(
`[RATE-LIMIT] Retry after ${error.retryAfter}ms`
);
return "retry-with-backoff";
})
),
Effect.catchAll((error) =>
Effect.gen(function* () {
yield* Effect.log(`[SUCCESS] Got response`);
return "completed";
})
)
);
yield* Effect.log(` Strategy: ${strategy}\n`);
}
// Example 3: Custom retry strategy based on error
console.log(`[3] Error-specific retry strategies:\n`);
let attemptCount = 0;
const networkOperation = Effect.gen(function* () {
attemptCount++;
yield* Effect.log(`[ATTEMPT] ${attemptCount}`);
if (attemptCount === 1) {
yield* Effect.fail(
new NetworkError({
endpoint: "api.example.com",
statusCode: 502,
retryable: true,
})
);
}
if (attemptCount === 2) {
yield* Effect.fail(
new RateLimitError({
retryAfter: 100,
})
);
}
return "success";
});
// Type-safe retry with error classification
let result3: string | null = null;
for (let i = 0; i < 3; i++) {
result3 = yield* networkOperation.pipe(
Effect.catchTag("NetworkError", (error) =>
Effect.gen(function* () {
if (error.retryable && i < 2) {
yield* Effect.log(`[RETRY] Network error is retryable`);
return null; // Signal to retry
}
yield* Effect.log(`[FAIL] Network error not retryable`);
return Effect.fail(error);
})
),
Effect.catchTag("RateLimitError", (error) =>
Effect.gen(function* () {
yield* Effect.log(
`[BACKOFF] Rate limited, waiting ${error.retryAfter}ms`
);
yield* Effect.sleep(`${error.retryAfter} millis`);
return null; // Signal to retry
})
),
Effect.catchAll((error) =>
Effect.gen(function* () {
yield* Effect.log(`[ERROR] Unhandled: ${error}`);
return Effect.fail(error);
})
)
).pipe(
Effect.catchAll(() => Effect.succeed(null))
);
if (result3 !== null) {
break;
}
}
yield* Effect.log(`\n[RESULT] ${result3}\n`);
// Example 4: Error-aware business logic
console.log(`[4] Business logic with error handling:\n`);
interface User {
id: string;
email: string;
}
const loadUser = (id: string): Effect.Effect<User, NetworkError | NotFoundError> =>
Effect.gen(function* () {
if (id === "invalid") {
yield* Effect.fail(
new NotFoundError({
resource: "user",
id,
})
);
}
if (id === "network-error") {
yield* Effect.fail(
new NetworkError({
endpoint: "/api/users",
retryable: true,
})
);
}
return { id, email: `user-${id}@example.com` };
});
const processUser = (id: string) =>
loadUser(id).pipe(
Effect.catchTag("NotFoundError", (error) =>
Effect.gen(function* () {
yield* Effect.log(
`[BUSINESS] User not found: ${error.id}`
);
// Return default/empty user
return { id: "", email: "" };
})
),
Effect.catchTag("NetworkError", (error) =>
Effect.gen(function* () {
yield* Effect.log(
`[BUSINESS] Network error, will retry from cache`
);
return { id, email: "[email protected]" };
})
)
);
yield* processUser("valid-id");
yield* processUser("invalid");
yield* processUser("network-error");
// Example 5: Discriminated union for exhaustiveness
console.log(`\n[5] Exhaustiveness checking (compile-time safety):\n`);
const classifyError = (
error: NetworkError | ValidationError | AuthenticationError | PermissionError
): string => {
switch (error._tag) {
case "NetworkError":
return `network: ${error.statusCode}`;
case "ValidationError":
return `validation: ${error.field}`;
case "AuthenticationError":
return `auth: ${error.reason}`;
case "PermissionError":
return `permission: ${error.action}`;
// TypeScript ensures all cases covered
default:
const _exhaustive: never = error;
return _exhaustive;
}
};
const testError = new ValidationError({
field: "age",
reason: "Must be >= 18",
});
const classification = classifyError(testError);
yield* Effect.log(`[CLASSIFY] ${classification}`);
// Example 6: Recovery strategy chains
console.log(`\n[6] Chained recovery strategies:\n`);
const resilientOperation = Effect.gen(function* () {
yield* Effect.fail(
new RateLimitError({
retryAfter: 50,
})
);
});
const withRecovery = resilientOperation.pipe(
Effect.catchTag("RateLimitError", (error) =>
Effect.gen(function* () {
yield* Effect.log(
`[STEP 1] Caught rate limit, waiting ${error.retryAfter}ms`
);
yield* Effect.sleep(`${error.retryAfter} millis`);
// Try again
return yield* Effect.succeed("recovered");
})
),
Effect.catchTag("NetworkError", (error) =>
Effect.gen(function* () {
if (error.retryable) {
yield* Effect.log(`[STEP 2] Network error, retrying...`);
return "retry";
}
return yield* Effect.fail(error);
})
),
Effect.catchAll((error) =>
Effect.gen(function* () {
yield* Effect.log(`[STEP 3] Final fallback`);
return "fallback";
})
)
);
yield* withRecovery;
});
Effect.runPromise(program);
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.
- 3d ago First seen · 461 lines · 2,660 tokens per session scan A 1b3cd1a46a02
error-handling-pattern-3-custom-error-strategies is a cursor rule published in the GitHub repository PaulJPhilp/EffectPatterns (796 stars, last pushed 2mo ago), licensed MIT. It adds 2,660 tokens to every session, about $0.0133 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-09-03.
Other cursor rules, from other repositories
fastapi-middleware
Specifies the use of middleware for logging, error monitoring, and performance optimization in FastAPI applications.
service-class-conventions
Defines the structure and implementation of service classes, enforcing the use of interfaces, ServiceImpl classes, DTOs for data transfer, and transactional management.
htmx-go-and-fiber-best-practices-general
Applies general best practices for HTMX, Go, and Fiber development to Go files. Focuses on Fiber framework usage.
fastapi-startup-and-shutdown-events
Recommends minimizing the use of startup and shutdown events in favor of lifespan context managers.
dragonruby-error-handling
Defines error handling and validation strategies within Ruby code in DragonRuby projects.
django-middleware-for-request-response
Recommends utilizing Django's middleware for handling request and response processing. Middleware allows for global actions to be performed on requests and responses.