# Error Handling Strategies in Karakeep: Typed TRPC Errors, Retry Logic, and Observability

> Explore Karakeep's robust error handling: typed TRPC errors, retry logic, and observability. Learn about its four-layered architecture for API consistency and defensive programming.

- Repository: [Karakeep App/karakeep](https://github.com/karakeep-app/karakeep)
- Tags: deep-dive
- Published: 2026-07-07

---

**Karakeep implements a four-layered error handling architecture that combines typed TRPC errors for API consistency, a functional tryCatch utility for defensive programming, structured retry mechanisms with back-off delays, and event-log middleware for comprehensive observability.**

Karakeep (formerly Hoarder) is an open-source bookmarking application built in TypeScript that employs sophisticated error handling strategies to ensure reliability across its API surface and background worker processes. This article examines the specific patterns and utilities used in the karakeep-app/karakeep repository to manage failures gracefully, from user-facing API endpoints to resilient background job processing.

## Typed TRPC Errors for API Consistency

All public-facing API endpoints in Karakeep return consistent, machine-readable error objects using TRPC's `TRPCError` class. This approach ensures that clients receive a predictable error shape containing both a code and message, which is formally defined in [`packages/open-api/lib/errors.ts`](https://github.com/karakeep-app/karakeep/blob/main/packages/open-api/lib/errors.ts):

```typescript
export const ErrorSchema = z.object({
  code: z.string().describe("A machine-readable error code."),
  message: z.string().describe("A human-readable error message."),
}).openapi("Error");

```

When an operation fails, routers throw `TRPCError` with standardized codes such as `BAD_REQUEST`, `UNAUTHORIZED`, `FORBIDDEN`, or `NOT_FOUND`. For example, in [`packages/trpc/routers/users.ts`](https://github.com/karakeep-app/karakeep/blob/main/packages/trpc/routers/users.ts), authentication failures are handled explicitly:

```typescript
throw new TRPCError({
  code: "FORBIDDEN",
  message: errorMessage,
});

```

Because these errors are typed and consistent, web clients, mobile applications, and SDKs can programmatically branch on `error.code` rather than parsing free-form text strings.

## The tryCatch Utility for Defensive Programming

Background workers in Karakeep frequently interact with external services such as Stripe, browser instances, or third-party feeds. Rather than allowing raw exceptions to bubble up and crash the process, workers use the `tryCatch` helper from [`packages/shared/tryCatch.ts`](https://github.com/karakeep-app/karakeep/blob/main/packages/shared/tryCatch.ts) (imported via `@karakeep/shared/tryCatch`). This utility wraps asynchronous calls and returns a discriminated union of `{ data, error }`, preventing unhandled promise rejections:

```typescript
// Example pattern from crawler worker implementations
const { data, error } = await tryCatch(fetchPage(url));
if (error) {
  // Worker decides whether to retry, log, or abort
}

```

By converting thrown exceptions into values, the `tryCatch` pattern makes error handling explicit and forces developers to consider failure cases at the call site. This is particularly critical in long-running worker processes where a single unhandled exception could terminate the entire job queue consumer.

## Retry and Back-Off Mechanisms in Workers

For transient failures such as network timeouts, rate limits, or temporarily blocked pages, Karakeep implements structured retry logic using `QueueRetryAfterError`. Workers signal the job queue system to retry a task after a specified delay by throwing this error with a custom back-off duration.

The crawler worker in [`apps/workers/workers/crawlerWorker.ts`](https://github.com/karakeep-app/karakeep/blob/main/apps/workers/workers/crawlerWorker.ts) demonstrates this pattern when encountering rate-limited domains:

```typescript
throw new QueueRetryAfterError(
  `[Crawler][${jobId}] Domain "${hostname}" is rate limited. Will retry …`,
  retryAfterMs
);

```

Similarly, the webhook worker in [`apps/workers/workers/webhookWorker.ts`](https://github.com/karakeep-app/karakeep/blob/main/apps/workers/workers/webhookWorker.ts) implements retry policies with configurable thresholds (typically controlled by `serverConfig.webhook.retryTimes`). This queue-driven approach ensures that temporary service outages do not result in permanent data loss while preventing infinite loops through maximum retry limits.

## Event-Log Middleware for Observability

Every state-changing operation in Karakeep is instrumented with event-log middleware to capture error context for debugging and audit trails. The `createEventLogMiddleware` function in [`packages/trpc/lib/eventLog.ts`](https://github.com/karakeep-app/karakeep/blob/main/packages/trpc/lib/eventLog.ts) automatically enriches logs with structured fields including `user.id`, `auth.failure_reason`, and the operation name (such as `bookmark.create`).

When an error occurs within a procedure, the middleware captures it before propagation. For example, in [`packages/trpc/routers/bookmarks.ts`](https://github.com/karakeep-app/karakeep/blob/main/packages/trpc/routers/bookmarks.ts), duplicate bookmark attempts are logged with specific error codes prior to throwing:

```typescript
addLogFields<"bookmark.create">({
  "error.code": "ALREADY_EXISTS",
});
throw new TRPCError({
  code: "BAD_REQUEST",
  message: "Bookmark already exists",
});

```

This middleware runs across all TRPC routers that perform mutations, ensuring consistent observability without requiring manual logging at every failure point.

## Summary

Karakeep's error handling strategies provide a robust foundation for production reliability:

- **Typed TRPC errors** guarantee a stable API contract for clients consuming the karakeep-app/karakeep API.
- **The `tryCatch` utility** isolates asynchronous failures in workers, preventing process crashes and enabling explicit error recovery logic.
- **QueueRetryAfterError** enables intelligent retry mechanisms with back-off delays for transient failures in background jobs.
- **Event-log middleware** automatically captures error context and audit trails across all state-changing operations.

## Frequently Asked Questions

### What error format does the Karakeep API return to clients?

The API returns standardized error objects defined by the `ErrorSchema` in [`packages/open-api/lib/errors.ts`](https://github.com/karakeep-app/karakeep/blob/main/packages/open-api/lib/errors.ts). Every error includes a machine-readable `code` (such as `BAD_REQUEST` or `UNAUTHORIZED`) and a human-readable `message` string, allowing clients to handle failures programmatically without parsing text.

### How does Karakeep handle rate limiting in the crawler worker?

When the crawler encounters a rate-limited domain, it throws `QueueRetryAfterError` with a calculated `retryAfterMs` delay. The queue system catches this specific error in [`apps/workers/workers/crawlerWorker.ts`](https://github.com/karakeep-app/karakeep/blob/main/apps/workers/workers/crawlerWorker.ts) and automatically re-queues the job for later execution, preventing data loss while respecting the target server's rate limits.

### What is the purpose of the tryCatch utility in Karakeep?

The `tryCatch` utility in [`packages/shared/tryCatch.ts`](https://github.com/karakeep-app/karakeep/blob/main/packages/shared/tryCatch.ts) wraps asynchronous operations to return `{ data, error }` objects instead of throwing exceptions. This pattern allows background workers to handle external service failures gracefully, deciding whether to retry operations, log errors, or abort without crashing the worker process.

### How does Karakeep log error context for debugging?

Karakeep uses `createEventLogMiddleware` from [`packages/trpc/lib/eventLog.ts`](https://github.com/karakeep-app/karakeep/blob/main/packages/trpc/lib/eventLog.ts) to automatically capture structured error context including user IDs, operation names, and failure reasons. This middleware integrates with all TRPC routers, ensuring that error details are logged consistently before being returned to the client.