Skip to content

Malformed memory-context locators escape validation as HTTP 500 #1879

Closed

Description

@phernandez

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 Core 8dcfcb3c50fde49205f703cfacb95b1f9a823fde. 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_context failed before context traversal.
  • Trigger shape, with invented values: workspace/project/https://example.invalid/notes embedded inside a memory path. No customer locator or note content is reproduced here.

Root-cause analysis

  1. An invalid locator containing an embedded protocol scheme reaches the memory-context route.
  2. schemas/memory.py:normalize_memory_url correctly rejects that malformed memory path.
  3. api/v2/routers/memory_router.py:get_memory_context calls normalization at line 194 without a typed input-rejection boundary. Its later handler only addresses ambiguous identifiers during traversal.
  4. The ValueError escapes 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_context records 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

  1. claude commented on Oct 27, 2026

    @claude
    Contributor

    Claude finished @phernandez's task in 15s —— View job


    Triage

    • Type: Bug (input error handling)
    • Component: API (v2 memory router), with an MCP-to-API locator seam to audit. This is not directly Cloud-specific behavior, so the Cloud label was not applied. Production evidence only shows where it was observed.
    • Priority: Medium. Invalid caller input returns HTTP 500 instead of a 4xx, which pollutes the error signal. There is no data loss, and no write is involved.
    • Complexity: Simple to Medium. The fix is a typed validation boundary in get_memory_context. The acceptance criteria also add endpoint tests, an MCP regression, and bounded span outcomes (success, input_rejected, failed).
    • Duplicates: None found. Searches for normalize_memory_url and the memory-context 500/ValueError returned no matching issue.
    • Reproduction: The issue already has production trace evidence and a clear root cause. No further repro steps are needed.

    The issue already specifies scope and acceptance criteria. Per triage-only scope, I made no code changes. Labels set: bug.

  2. added a commit that references this issue on Oct 28, 2026
    6d202cf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingproductionObserved in production

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions