.agents/skills/error-handling/SKILL.md
Error handling patterns using wellcrafted trySync and tryAsync, and toastOnError for surfacing errors to users. Use when writing or reviewing try-catch blocks, refactoring try-catch to linear control flow, working with Result types, showing error toasts, or returning HTTP error responses from route handlers.
npx skillsauth add epicenterhq/epicenter error-handlingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Use this pattern when you need to:
try-catch blocks with trySync or tryAsync.Ok(...) and propagate failures with Err(...).cause for typed domain error constructors.Load these on demand based on what you're working on:
toastOnError, extractErrorMessage in UI), read references/toast-on-error.mdWhen handling errors that can be gracefully recovered from, use trySync (for synchronous code) or tryAsync (for asynchronous code) from wellcrafted instead of traditional try-catch blocks. This provides better type safety and explicit error handling.
Related Skills: See
services-layerskill fordefineErrorspatterns and service architecture. Seequery-layerskill for error transformation toWhisperingError.
import { trySync, tryAsync, Ok, Err } from 'wellcrafted/result';
// SYNCHRONOUS: Use trySync for sync operations
const { data, error } = trySync({
try: () => {
const parsed = JSON.parse(jsonString);
return validateData(parsed); // Automatically wrapped in Ok()
},
catch: (e) => {
// Gracefully handle parsing/validation errors
console.log('Using default configuration');
return Ok(defaultConfig); // Return Ok with fallback
},
});
// ASYNCHRONOUS: Use tryAsync for async operations
await tryAsync({
try: async () => {
const child = new Child(session.pid);
await child.kill();
console.log(`Process killed successfully`);
},
catch: (e) => {
// Gracefully handle the error
console.log(`Process was already terminated`);
return Ok(undefined); // Return Ok(undefined) for void functions
},
});
// Both support the same catch patterns
const syncResult = trySync({
try: () => riskyOperation(),
catch: (error) => {
// For recoverable errors, return Ok with fallback value
return Ok('fallback-value');
// For unrecoverable errors, pass the raw cause — the constructor handles extractErrorMessage
return CompletionError.ConnectionFailed({ cause: error });
},
});
trySync for synchronous code, tryAsync for asynchronous codeT, the catch should return Ok<T> for graceful handlingOk(undefined) in the catchErr when you want to propagate the errorcause: unknown and let the defineErrors constructor call extractErrorMessage(cause) inside its message template. Don't call extractErrorMessage at the call site. This centralizes message extraction where the message is composed:// ✅ GOOD: cause: error at call site, extractErrorMessage in constructor
catch: (error) => CompletionError.ConnectionFailed({ cause: error })
// ❌ BAD: extractErrorMessage at call site, string passed to constructor
catch: (error) => CompletionError.ConnectionFailed({ underlyingError: extractErrorMessage(error) })
{ data, error } from tryAsync/trySync, the error variable is the raw error value, NOT wrapped in Err. You must wrap it before returning:// WRONG - error is just the raw error value, not a Result
const { data, error } = await tryAsync({...});
if (error) return error; // TYPE ERROR: Returns raw error, not Result
// CORRECT - wrap with Err() to return a proper Result
const { data, error } = await tryAsync({...});
if (error) return Err(error); // Returns Err<CustomError>
This is different from returning the entire result object:
// This is also correct - userResult is already a Result type
const userResult = await tryAsync({...});
if (userResult.error) return userResult; // Returns the full Result
// SYNCHRONOUS: JSON parsing with fallback
const { data: config } = trySync({
try: () => JSON.parse(configString),
catch: (e) => {
console.log('Invalid config, using defaults');
return Ok({ theme: 'dark', autoSave: true });
},
});
// SYNCHRONOUS: File system check
const { data: exists } = trySync({
try: () => fs.existsSync(path),
catch: () => Ok(false), // Assume doesn't exist if check fails
});
// ASYNCHRONOUS: Graceful process termination
await tryAsync({
try: async () => {
await process.kill();
},
catch: (e) => {
console.log('Process already dead, continuing...');
return Ok(undefined);
},
});
// ASYNCHRONOUS: File operations with fallback
const { data: content } = await tryAsync({
try: () => readFile(path),
catch: (e) => {
console.log('File not found, using default');
return Ok('default content');
},
});
// EITHER: Error propagation (works with both)
// Pass the raw caught error as cause — the defineErrors constructor calls extractErrorMessage
const { data, error } = await tryAsync({
try: () => criticalOperation(),
catch: (error) =>
CompletionError.ConnectionFailed({ cause: error }),
});
if (error) return Err(error);
Use trySync when:
Use tryAsync when:
Use traditional try-catch when:
documentation
Yjs CRDT patterns, shared types (Y.Map, Y.Array, Y.Text), conflict resolution, and document storage. Use when the user mentions Yjs, Y.Doc, CRDTs, collaborative editing, or when handling shared types, implementing real-time sync, or optimizing document storage.
tools
Voice and tone rules for all written content—prose, UI text, tooltips, error messages. Use when the user says "fix the tone", "rewrite this", "sounds like AI", "sounds corporate", or when writing any user-facing text, landing pages, product copy, or open-source documentation.
tools
Workspace API patterns for defineTable, defineKv, versioning, migrations, data access (CRUD + observation), withActions, and extension ordering. Use when the user mentions workspace, defineTable, defineKv, createWorkspace, withActions, withExtension, defineQuery, defineMutation, connectWorkspace, or when defining schemas, reading/writing table data, observing changes, writing migrations, chaining extensions, or attaching actions to a workspace client.
documentation
Standard workflow for implementing features with specs and planning documents. Use when the user says "start a new feature", "how should I plan this", "what's the process", or when starting implementation, planning work, or working on any non-trivial task.