OpenSEO Error Handling Patterns: A Complete Guide to Type-Safe Failure Management

OpenSEO uses a layered, type-safe error-handling architecture built around the AppError class, centralized middleware, and selective telemetry to PostHog.

The OpenSEO repository implements a disciplined approach to error handling in TypeScript that spans both server and client environments. Understanding these patterns is essential for contributing to the codebase or adapting similar strategies in your own projects.

Core Architecture: Four Layers of Error Handling

OpenSEO organizes error handling into distinct layers, each with a specific responsibility. This separation ensures business logic remains clean while failures are consistently tracked and safely exposed.

Domain Errors with AppError

At the foundation lies the AppError class defined in src/server/lib/errors.ts. This class represents business-logic failures using stable error codes rather than fragile string messages.

import { AppError } from '@/server/lib/errors';

throw new AppError('PROJECT_NOT_FOUND', `Project ${id} does not exist`);

The constructor signature requires:

  • A code from the canonical ERROR_CODES list
  • An optional human-readable message
  • Optional additional context

Validation Error Conversion

TanStack server-function validation failures are normalized through the isValidatorError helper in src/middleware/errorHandling.ts. This helper detects validator errors and transforms them into AppError instances with the VALIDATION_ERROR code.

Error Normalization Utilities

The same middleware file provides two critical conversion functions:

  • asAppError(error) — Attempts to coerce any thrown value to an AppError, returning null if impossible
  • toClientError(appError) — Strips internal details to produce a safe, serializable error object
import { asAppError, toClientError } from '@/server/lib/errors';

try {
  await riskyOperation();
} catch (e) {
  const appError = asAppError(e) ?? new AppError('INTERNAL_ERROR');
  throw toClientError(appError); // Safe for client consumption
}

Error Handling Middleware: The Central Guard

The errorHandlingMiddleware in src/middleware/errorHandling.ts wraps every server function to apply uniform error processing. It integrates with TanStack's middleware system via createMiddleware.

Execution Flow

  1. Wraps the downstream handler in a try/catch block
  2. Classifies caught errors (validator vs. unknown vs. AppError)
  3. Coerces non-AppError values using asAppError
  4. Filters telemetry via shouldCaptureAppErrorCode
  5. Reports eligible errors to PostHog through captureServerError
  6. Sanitizes the response using toClientError before re-throwing

Request Context Capture

When reporting occurs, the middleware gathers:

  • HTTP method and path
  • Resolved user distinct ID via resolveErrorDistinctId
  • Authenticated state (without exposing tokens)
import { errorHandlingMiddleware } from '@/middleware/errorHandling';
import { createServerFn } from '@tanstack/react-start/server';

export const secureServerFn = createServerFn()
  .use(errorHandlingMiddleware) // Installs full error stack
  .handler(async (params) => {
    // Your logic here; all errors handled uniformly
  });

Global registration happens in src/serverFunctions/middleware.ts, ensuring every server function inherits this protection.

Telemetry Control and Observability

OpenSEO implements selective error reporting to avoid analytics noise. The src/shared/error-codes.ts file defines both the canonical error code list and the filtering logic.

Opting In or Out of Reporting

// src/shared/error-codes.ts
export const ERROR_CODES = [
  'PROJECT_NOT_FOUND',
  'VALIDATION_ERROR',
  'AUTH_CONFIG_MISSING',
  'CUSTOM_FEATURE_LIMIT_EXCEEDED',
  // ... more codes
] as const;

export function shouldCaptureAppErrorCode(code?: string): boolean {
  const BLOCKED = new Set(['AUTH_CONFIG_MISSING', 'RATE_LIMIT_SILENT']);
  return !BLOCKED.has(code ?? '');
}

Only errors passing this filter reach captureServerError in src/server/lib/posthog.ts, which asynchronously sends structured data to PostHog inside waitUntil to avoid blocking responses.

Security: Client-Safe Error Exposure

The toClientError function enforces a critical boundary: internal details never leak to the client. The transformation:

  • Preserves only the error code and safe message
  • Removes stack traces
  • Strips any internal context objects
  • Falls back to a generic message for truly unexpected errors

This pattern satisfies security requirements while maintaining debugging capability through server-side logs and telemetry.

Key Files Reference

File Responsibility
src/server/lib/errors.ts AppError class, asAppError, toClientError
src/middleware/errorHandling.ts Central middleware, validator detection, request context
src/shared/error-codes.ts Canonical codes, shouldCaptureAppErrorCode
src/server/lib/posthog.ts captureServerError, PostHog integration
src/serverFunctions/middleware.ts Global middleware registration

Summary

  • Type safety first: All errors flow through AppError with compile-time code validation
  • Middleware encapsulation: errorHandlingMiddleware provides consistent behavior without boilerplate
  • Selective telemetry: shouldCaptureAppErrorCode prevents analytics pollution
  • Security by default: toClientError guarantees safe client exposure
  • Observability: PostHog integration with request context enables effective debugging

Frequently Asked Questions

How does OpenSEO distinguish between validation and domain errors?

The isValidatorError helper in src/middleware/errorHandling.ts detects TanStack validator errors by checking error structure, then wraps them as AppError instances with code VALIDATION_ERROR. Domain errors are thrown directly as AppError instances from business logic.

What happens when an unexpected non-Error value is thrown?

The asAppError function attempts to extract meaningful information. If coercion fails, the middleware creates a generic AppError('INTERNAL_ERROR'). This ensures type safety even when third-party code throws primitives or unexpected objects.

Can I disable error reporting for specific error codes?

Yes. Add the code to the BLOCKED Set in src/shared/error-codes.ts within shouldCaptureAppErrorCode. Errors with blocked codes proceed through normal handling but skip the captureServerError telemetry step.

How does authentication affect error tracking?

The resolveErrorDistinctId function in the error handling middleware extracts the authenticated user's analytics ID when available. This attaches user context to PostHog events without exposing identifiers in client-facing responses.

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 →