# How Open-Notebook Uses a Typed Exception Hierarchy for Robust Error Handling

> Learn how Open-Notebook achieves robust error handling with its typed exception hierarchy. Discover how it classifies provider failures and standardizes HTTP responses for better API management.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: best-practices
- Published: 2026-06-14

---

**Open-Notebook centralizes error handling through a typed exception hierarchy rooted in `OpenNotebookError`, classifies raw provider failures via `classify_error`, and converts them to standardized HTTP responses using FastAPI exception handlers.**

Open-Notebook implements a robust error management system that transforms unpredictable third-party failures into consistent, user-friendly API responses. The architecture relies on a **typed exception hierarchy** defined in [`open_notebook/exceptions.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/exceptions.py) to categorize errors ranging from database failures to AI provider timeouts. This design ensures that every failure bubbling up from the domain layer or external services is handled predictably and returned to the client with appropriate HTTP status codes and CORS headers.

## The Core Exception Hierarchy

All application-specific errors in Open-Notebook inherit from **`OpenNotebookError`**, a base class that enables catching any notebook-related failure in a single `except` clause. This inheritance structure provides type safety and simplifies error propagation across the codebase.

The concrete subclasses cover distinct failure domains:

- **`DatabaseOperationError`** – Wraps database query or transaction failures raised in `open_notebook/domain/*.py`
- **`UnsupportedTypeException`** – Indicates invalid types supplied to utility helpers
- **`InvalidInputError`** – Signals validation errors for user-provided data, commonly raised in domain models like [`open_notebook/domain/notebook.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/notebook.py)
- **`NotFoundError`** – Indicates requested records do not exist in repository or service layers
- **`AuthenticationError`** – Represents bad credentials or invalid authentication tokens in auth middleware
- **`ConfigurationError`** – Flags misconfigured settings or missing provider models, typically raised in [`open_notebook/ai/provision.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/provision.py)
- **`ExternalServiceError`** – Encapsulates remote AI provider errors processed through [`open_notebook/utils/error_classifier.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/error_classifier.py)
- **`RateLimitError`** – Signifies provider rate-limit violations
- **`FileOperationError`** – Covers file I/O problems including upload and deletion failures
- **`NetworkError`** – Represents network-level failures such as timeouts and DNS errors
- **`NoTranscriptFound`** – Indicates video-to-text extraction failures in podcast workflows

These definitions reside in [`open_notebook/exceptions.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/exceptions.py) and serve as the foundation for the error classification pipeline.

## Classifying Raw Provider Exceptions

When third-party libraries like Esperanto or LangChain raise native exceptions, Open-Notebook normalizes them through the **`classify_error`** utility in [`open_notebook/utils/error_classifier.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/error_classifier.py). This function maps unpredictable provider errors to the typed hierarchy using keyword matching.

The classification logic iterates through `_CLASSIFICATION_RULES` to match exception messages against specific keywords:

```python

# open_notebook/utils/error_classifier.py

def classify_error(exception: BaseException) -> tuple[type[OpenNotebookError], str]:
    ...
    for keywords, exc_class, message in _CLASSIFICATION_RULES:
        for keyword in keywords:
            if keyword in combined:
                user_message = message if message is not None else _truncate(str(exception))
                return exc_class, user_message
    # fallback → ExternalServiceError

```

The rule table matches fragments like *"authentication"*, *"rate limit"*, *"timeout"*, or HTTP status codes like *"413"*, converting them into appropriate typed exceptions with user-friendly messages. Unmatched errors default to `ExternalServiceError` and are logged for monitoring.

## Raising Typed Exceptions in Domain Logic

Domain objects and services raise concrete exceptions directly when detecting invalid states or catching low-level failures. This pattern appears throughout the codebase, ensuring that every error bubbles up as a subclass of `OpenNotebookError`.

For example, in the notebook domain model:

```python

# open_notebook/domain/notebook.py (excerpt)

if not name.strip():
    raise InvalidInputError("Notebook name cannot be empty")
...
except SomeDatabaseException as e:
    raise DatabaseOperationError(e)

```

Similar patterns exist in [`open_notebook/domain/base.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/base.py) and [`open_notebook/database/repository.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/database/repository.py), where raw database exceptions are caught and re-raised as `DatabaseOperationError` to maintain abstraction boundaries.

## Converting Exceptions to HTTP Responses

The API layer in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) registers dedicated FastAPI exception handlers for each typed exception. These handlers perform three critical functions:

1. Set appropriate HTTP status codes (e.g., 400 for `InvalidInputError`, 404 for `NotFoundError`, 401 for `AuthenticationError`)
2. Return JSON payloads formatted as `{"detail": "<message>"}`
3. Append CORS headers via the `_cors_headers` helper to ensure browser accessibility

```python

# api/main.py (excerpt)

@app.exception_handler(InvalidInputError)
async def invalid_input_error_handler(request: Request, exc: InvalidInputError):
    return JSONResponse(
        status_code=400,
        content={"detail": str(exc)},
        headers=_cors_headers(request),
    )

```

A generic handler for `OpenNotebookError` catches any uncaught subclass and returns a 500 response, preventing unhandled exceptions from leaking stack traces to clients.

## End-to-End Error Flow Example

Consider a scenario where a client uploads a file exceeding the AI provider's payload limit:

1. **Provider layer** raises a low-level `HTTPError` with message *"413 Payload Too Large"*
2. **`classify_error`** matches the keyword `"413"` and returns `ExternalServiceError` with the message *"The request payload is too large..."*
3. The workflow re-raises the `ExternalServiceError`
4. FastAPI catches it via `external_service_error_handler` (status **502**) and returns a JSON error with CORS headers

```python

# Pseudo-code inside a graph node

try:
    await provider.generate(...)
except Exception as exc:
    exc_class, msg = classify_error(exc)
    raise exc_class(msg) from exc

```

The resulting HTTP response:

```json
{
  "detail": "The request payload is too large for the AI provider. Try reducing the content size or using a different model."
}

```

## Summary

- **Open-Notebook** defines a centralized exception hierarchy in [`open_notebook/exceptions.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/exceptions.py) with `OpenNotebookError` as the base class and specific subclasses for database, authentication, configuration, and external service failures.
- The **`classify_error`** function in [`open_notebook/utils/error_classifier.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/error_classifier.py) normalizes raw third-party exceptions into typed errors using keyword matching rules.
- Domain layers raise typed exceptions directly, while repository layers wrap low-level database errors to maintain clean architecture boundaries.
- FastAPI handlers in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) convert each typed exception into appropriate HTTP responses with proper status codes and CORS headers.
- This pipeline ensures that unpredictable provider failures become consistent, actionable API errors.

## Frequently Asked Questions

### What is the base exception class in Open-Notebook?

The base exception class is **`OpenNotebookError`**, defined in [`open_notebook/exceptions.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/exceptions.py). All application-specific exceptions inherit from this class, allowing developers to catch any notebook-related error with a single `except OpenNotebookError` clause while maintaining type safety for specific error handling.

### How does Open-Notebook handle errors from third-party AI providers?

Open-Notebook processes raw provider exceptions through the **`classify_error`** function in [`open_notebook/utils/error_classifier.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/error_classifier.py). This utility matches error messages against keyword rules (e.g., "rate limit", "timeout", "413") and maps them to appropriate typed exceptions like `RateLimitError` or `ExternalServiceError`, translating technical failures into user-friendly messages.

### Which HTTP status codes does Open-Notebook return for different error types?

The FastAPI exception handlers in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) map specific exceptions to semantic HTTP status codes: `InvalidInputError` returns **400**, `NotFoundError` returns **404**, `AuthenticationError` returns **401**, `RateLimitError` returns **429**, and `ExternalServiceError` returns **502**. A generic handler catches any remaining `OpenNotebookError` subclasses with status **500**.

### Where should I add new exception types in the Open-Notebook codebase?

New exception types should be defined in **[`open_notebook/exceptions.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/exceptions.py)** by inheriting from `OpenNotebookError`. After defining the class, register a corresponding handler in **[`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py)** that returns the appropriate HTTP status code and CORS headers, following the pattern of existing handlers like `invalid_input_error_handler`.