# Error Handling Pattern **Last Updated:** 2026-01-31 **Confidence:** High ## Summary StemeDB uses `thiserror` for library errors with context chains. No panics in production code. All fallible operations return `Result`. **Key Facts:** - Library code: `thiserror` for custom error types — ALL error enums MUST use `#[derive(thiserror::Error)]` - Binary code: `anyhow` for error chaining - Never use `unwrap()`, `expect()`, `panic!()` in production - Add context with `.context("what we were doing")?` - NEVER use manual `impl Display` + `impl Error` for error types — use thiserror derives instead **Error types in workspace (all use thiserror):** - `stemedb-core/src/serde.rs` — `SerdeError` - `stemedb-wal/src/error.rs` — `QuarantineError` - `stemedb-storage/src/error.rs` — `StorageError` - `stemedb-ingest/src/error.rs` — `IngestError` - `stemedb-query/src/error.rs` — `QueryError` - `stemedb-api/src/error.rs` — `ApiError` ## The Pattern ```rust use thiserror::Error; #[derive(Debug, Error)] pub enum StemeError { #[error("assertion not found: {0:?}")] NotFound(Hash), #[error("invalid signature for agent {agent:?}")] InvalidSignature { agent: AgentId }, #[error("storage error: {0}")] Storage(String), #[error("serialization error: {0}")] Serialization(String), } // Usage with context fn load_assertion(&self, hash: &Hash) -> Result { let bytes = self.store .get(hash.as_bytes()) .context("failed to read assertion from store")? .ok_or(StemeError::NotFound(*hash))?; rkyv::from_bytes(&bytes) .map_err(|e| StemeError::Serialization(e.to_string())) } ``` ## Error Categories | Type | Description | Example | |------|-------------|---------| | `NotFound` | Data doesn't exist | Missing assertion | | `InvalidSignature` | Crypto verification failed | Tampered assertion | | `Storage` | Underlying KV error | Disk full | | `Serialization` | Encode/decode failed | Corrupt data | ## Related Topics - [Rust Guidelines](../../.claude/guides/backend/rust-guidelines.md) - [Coding Guidelines](../../../.claude/guides/coding-guidelines.md)