---
title: Error Handling Patterns
category: constraints
severity: high
---

# Error Handling Patterns

## Core Rules

1. **Catch `Exception`, return safe fallbacks** (False/[]/None)
2. **Log exceptions with traceback** using `_logger.exception()`
3. **Never swallow exceptions silently** - always log or return sensible default
4. **Avoid nested try/except blocks** - flatten exception handling

## Pattern: Try/Except Safe Fallback

This is the dominant pattern in the codebase (219+ instances).

```python
# Standard pattern from database.py, api_client.py, etc.
try:
    result = risky_operation()
    return process(result)
except Exception as exc:
    _logger.warning("Operation failed: %s", exc)
    return safe_fallback  # False, [], None, {}
```

### Examples from Codebase

**database.py** - DuckDB operations:
```python
def get_svd_vectors(self, window: str):
    try:
        conn = duckdb.connect(self.db_path, read_only=True)
        try:
            result = conn.execute(query, (window,)).fetchall()
            return self._parse_vectors(result)
        finally:
            conn.close()
    except Exception as exc:
        _logger.warning("Failed to get SVD vectors: %s", exc)
        return []
```

**ai_provider.py** - HTTP retries:
```python
try:
    resp = requests.post(url, json=json, headers=headers, timeout=10)
    resp.raise_for_status()
    return resp.json()
except requests.ConnectionError as exc:
    if attempt == retries:
        raise ProviderError(f"Connection error: {exc}") from exc
    # ... retry logic
```

## Pattern: Optional Dependency Fallback

Gracefully degrade when optional packages are unavailable.

```python
# UMAP fallback in explorer_helpers.py
try:
    import umap
    HAS_UMAP = True
except ImportError:
    HAS_UMAP = False
    _logger.debug("UMAP not available, using SVD vectors directly")

def project_to_2d(vectors):
    if HAS_UMAP:
        return umap.UMAP().fit_transform(vectors)
    return vectors[:, :2]  # Fallback: first 2 SVD dimensions
```

## Anti-Patterns

### 1. Bare except with pass (CRITICAL)
**File**: `database.py`, line 47

```python
# BAD - catches KeyboardInterrupt, SystemExit, MemoryError
try:
    conn.execute("CREATE SEQUENCE IF NOT EXISTS motions_id_seq START 1")
except:  # bare except
    pass
```

**Fix**: Catch specific exception or log and continue:
```python
try:
    conn.execute("CREATE SEQUENCE IF NOT EXISTS motions_id_seq START 1")
except Exception as exc:
    _logger.debug("Sequence creation skipped (may already exist): %s", exc)
```

### 2. Nested Exception Handling
**File**: `explorer.py`, lines 244-261

```python
# BAD - opaque error paths
try:
    result = compute_svd(motions)
except Exception:
    try:
        result = fallback_compute(motions)
    except Exception:
        pass  # Both exceptions silently dropped
```

**Fix**: Flatten and handle each case explicitly:
```python
# GOOD - explicit handling
try:
    result = compute_svd(motions)
except Exception as exc:
    _logger.warning("SVD failed, trying fallback: %s", exc)
    try:
        result = fallback_compute(motions)
    except Exception as fallback_exc:
        _logger.error("Both SVD approaches failed: %s, %s", exc, fallback_exc)
        raise
```

## Rule Summary

| Pattern | When to Use | Return Value |
|---------|-------------|--------------|
| Safe fallback | Best-effort operations | `[]`, `{}`, `False`, `None` |
| Re-raise | Critical operations that must succeed | raise |
| Log and continue | Optional steps in pipeline | (continue) |
| Graceful degradation | Optional dependencies | Default behavior |

## When to Log vs Return

| Scenario | Action |
|----------|--------|
| User action fails | Log warning, return safe default |
| Internal error (corrupt data) | Log error, return safe default |
| Transient failure (network) | Log warning, retry if appropriate |
| Configuration error | Log error, raise with clear message |
