Skip to content

Structured error handling and typed REST response models#71

Merged
imonroe merged 3 commits into
mainfrom
claude/wizardly-planck-pwvl93-i68
Jun 9, 2026
Merged

Structured error handling and typed REST response models#71
imonroe merged 3 commits into
mainfrom
claude/wizardly-planck-pwvl93-i68

Conversation

@imonroe

@imonroe imonroe commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Closes #68

What

Stable, sanitized errors. Backend failures previously surfaced as opaque 500s that could leak internals. Now every failure returns:

{"detail": "...", "error": "machine_code", "request_id": "abc123def456"}
Failure Status error
Qdrant unreachable / erroring (qdrant-client, httpx transport/status, ConnectionError, Timeout) 503 backend_unavailable
LLM/embedder provider failure (any openai.OpenAIError / anthropic.AnthropicError) 502 upstream_provider_error
Anything else 500 internal_error

Exception text never reaches the client; the full traceback is logged server-side with the same request_id that's in the body (settable via X-Request-Id), so errors correlate with log lines.

404 consistency. PUT/DELETE on a missing memory ID now 404 like GET (mem0 otherwise raises or silently no-ops depending on version). The MCP get/update/delete tools return a structured {"error": "not_found", "memory_id": ...} instead of null or a raw tool exception, giving the model a usable signal.

Typed responses. Every REST route gains a Pydantic response model — MemoryItem, MemoryResults, AddMemoryResponse (incl. the dedup-marker fields), UpdateResponse, DeleteResponse, HistoryResponse — so /docs publishes real schemas for client generators and n8n. Models use extra="allow" + response_model_exclude_unset=True, so the wire format stays byte-identical to what mem0 returns: unknown fields pass through, absent fields aren't fabricated as nulls (covered by an explicit pass-through test).

Design note (deviation from the issue plan)

The issue proposed wrapping every mem0 call site in a translator. Implemented instead as a single app-level exception handler over a classifier of concrete SDK exception types (app/errors.py) — same behavior, zero call-site discipline required, and new mem0 call sites are covered automatically. Provider errors are checked before network errors because the openai/anthropic SDKs wrap httpx exceptions.

Also fixes a latent test-isolation bug found along the way: the shared mem fixture only called reset_mock(), which preserves return_value/side_effect across tests.

Tests

15 new tests (tests/test_errors.py + REST/MCP additions): classifier units (incl. provider-wins-over-wrapped-network ordering), 503/502/500 through the real app, no-leak assertion on exception text, X-Request-Id round-trip, REST 404s for update/delete, MCP not_found returns, unknown-field pass-through. Full suite: 181 passed, ruff clean.

Review

Adversarial review surfaced 7 candidates; 2 were fixed (added httpx.HTTPStatusError to the backend tuple; explicit is None check in the MCP get tool so a falsy-but-present result isn't misreported as missing). The rest were deliberate tradeoffs (e.g. response validation strictness is bounded to documented field types; history rows stay untyped because their shape is mem0-version-dependent).

https://claude.ai/code/session_01H2Dbh6kD8bseWZZEf7kGhx


Generated by Claude Code

claude added 3 commits June 9, 2026 20:11
Backend failures no longer surface as opaque 500s: a new app/errors.py
classifier sorts concrete SDK exceptions (qdrant-client/httpx -> 503
backend_unavailable, openai/anthropic -> 502 upstream_provider_error,
else 500 internal_error) and an app-level exception handler returns a
stable sanitized JSON body carrying the request_id for log correlation —
exception text never reaches the client.

PUT/DELETE on a missing memory ID now 404 like GET (mem0's update/delete
otherwise raises or silently no-ops depending on version), and the MCP
get/update/delete tools return a structured {"error": "not_found"}
instead of nulls or raw tool exceptions.

REST routes gain pass-through Pydantic response models (extra=allow +
response_model_exclude_unset) so /docs publishes real schemas while the
wire format stays byte-identical to what mem0 returns.

Also fixes a latent test-isolation bug: the shared mem fixture only did
reset_mock(), which keeps return_value/side_effect across tests.

Closes #68

https://claude.ai/code/session_01H2Dbh6kD8bseWZZEf7kGhx
@imonroe imonroe merged commit 0a7f035 into main Jun 9, 2026
1 check passed
@imonroe imonroe deleted the claude/wizardly-planck-pwvl93-i68 branch June 9, 2026 21:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Structured error handling and typed response models for the REST API

2 participants