Skip to content

SEP-2575 HTTP status mapping is timing-sensitive under load #1772

Description

@PranavSenthilnathan

Description

RawHttpConformanceTests.July2026Post_MissingRequiredCapability_Returns400 intermittently receives HTTP 200 instead of HTTP 400.

The server correctly produces a MissingRequiredClientCapabilityException and sends the corresponding JSON-RPC error. However, the HTTP status can already be committed as 200 before that error reaches the response callback.

Evidence

The failure occurred in:

The test log shows:

  1. requires_sampling threw MissingRequiredClientCapabilityException.
  2. The handler took approximately 219 ms.
  3. Kestrel completed the request with HTTP 200.
  4. The process did not crash; --blame-crash only attached its standard collector.

The same test previously failed with the same 200-versus-400 result on an earlier workflow attempt and then passed when rerun:

The failure has occurred under different configurations and target frameworks, while the other frameworks in the same jobs passed.

Root cause

StreamableHttpPostTransport defers committing response headers for a fixed 250 ms window:

internal static readonly TimeSpan DeferredHeaderFlushGrace =
    TimeSpan.FromMilliseconds(250);

The timer starts after the request is queued for dispatch. If dispatch and handler execution exceed that window, DeferredHeaderFlushAsync invokes the response-starting callback with null and flushes the default HTTP 200 headers.

When the MissingRequiredClientCapability JSON-RPC error arrives later, _httpResponseStarted is already true, so the callback cannot update the HTTP status to 400. The JSON-RPC error is therefore returned over an already-committed HTTP 200 response.

This makes the conformance test dependent on scheduling and machine load rather than solely on server behavior.

Note

This issue was drafted with GitHub Copilot.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-testsIssue related to tests

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions