Repository navigation
Description
Problem and production evidence
Malformed context locators are rejected by normalize_memory_url, but the v2 memory router lets its ValueError cross the HTTP boundary as an unhandled 500.
- Logfire #3295, UUID
0d23187f-3b8a-4902-9e3a-466f7b954b7a; one observed production occurrence at 2026-10-27 03:16:18 UTC. - Representative production trace.
- Cloud release
3b97bd5675c2a13b55bbf4acb27ced0c9403ab0b, pinned Core8dcfcb3c50fde49205f703cfacb95b1f9a823fde. Read-only Fly state and telemetry agree. Current Core main retains the same unhandled normalization call. GET /v2/projects/{project_id}/memory/{uri:path}returned 500 in 0.58s.api.request.memory.build_contextfailed before context traversal.- Trigger shape, with invented values:
workspace/project/https://example.invalid/notesembedded inside a memory path. No customer locator or note content is reproduced here.
Root-cause analysis
- An invalid locator containing an embedded protocol scheme reaches the memory-context route.
schemas/memory.py:normalize_memory_urlcorrectly rejects that malformed memory path.api/v2/routers/memory_router.py:get_memory_contextcalls normalization at line 194 without a typed input-rejection boundary. Its later handler only addresses ambiguous identifiers during traversal.- The
ValueErrorescapes FastAPI and is recorded by the unhandled-exception boundary. Invalid caller input becomes an internal-server-error response.
This is an input error-handling bug. Context retrieval failed; no durable write was attempted in this route. The validation rejection is expected, but its unhandled propagation is not. Lowering exception severity would hide the failed request rather than repair the boundary. No retry can make the malformed path valid.
A nearby bm_context product span records success=false; its Core child trace lacks a propagated request ID, so timing alone is not used as proof of cross-trace identity. The API trace and released source independently establish the HTTP bug. Why the client constructed this particular malformed locator remains unproven and is separate from the confirmed API boundary defect.
Production bmc env --json confirmed production/read-only. Admin health access needs renewed login; no login, replay or production mutation was performed. The read-only production operator queue query succeeded. No matching dedicated issue was found in searches for the exception/normalizer.
Fix scope
Construct/validate the accepted locator at the boundary and represent malformed input as an actionable bounded input rejection. Preserve rejection of embedded schemes; do not silently reinterpret an arbitrary HTTPS URL as a memory path. Keep unexpected failures from the actual context service visible. Audit the MCP-to-API locator seam so the same failure is surfaced as caller input feedback rather than an unexplained server failure.
Acceptance criteria
- Real FastAPI endpoint test for an embedded scheme returns the intended 4xx rejection and never invokes context traversal.
- Cover empty/whitespace, invalid characters and malformed separators, plus valid relative and
memory://locators. - MCP boundary regression uses the production-shaped locator and confirms actionable rejection without a server-error acknowledgement.
- Genuine context-service failures remain server failures; ambiguous identifiers preserve their existing conflict response.
api.request.memory.build_contextrecords bounded terminal outcomes (success,input_rejected,failed) without recording uploaded content; assert the input-rejected outcome through the endpoint.- Keep open until the fix is deployed and directly verified. No production deploy is included in this issue.
Activity
claude commentedon Oct 27, 2026
Claude finished @phernandez's task in 15s —— View job
Triage
get_memory_context. The acceptance criteria also add endpoint tests, an MCP regression, and bounded span outcomes (success,input_rejected,failed).normalize_memory_urland the memory-context 500/ValueErrorreturned no matching issue.The issue already specifies scope and acceptance criteria. Per triage-only scope, I made no code changes. Labels set:
bug.fix(api): return 400 for malformed memory-context locators (#1885)