dotnet/yarp · error · InvalidOperationException

The request cannot be forwarded, the response has already st

Error message

The request cannot be forwarded, the response has already started

What it means

Thrown by HttpForwarder.SendAsync when the response has already started (headers sent / body written) on the HttpContext. Once the response has started, YARP cannot replace it with the destination's response, so forwarding is impossible. The check uses RequestUtilities.IsResponseSet.

Source

Thrown at src/ReverseProxy/Forwarder/HttpForwarder.cs:112

        => SendAsync(context, destinationPrefix, httpClient, requestConfig, transformer, CancellationToken.None);

    public async ValueTask<ForwarderError> SendAsync(
        HttpContext context,
        string destinationPrefix,
        HttpMessageInvoker httpClient,
        ForwarderRequestConfig requestConfig,
        HttpTransformer transformer,
        CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentNullException.ThrowIfNull(destinationPrefix);
        ArgumentNullException.ThrowIfNull(httpClient);
        ArgumentNullException.ThrowIfNull(requestConfig);
        ArgumentNullException.ThrowIfNull(transformer);

        if (RequestUtilities.IsResponseSet(context.Response))
        {
            throw new InvalidOperationException("The request cannot be forwarded, the response has already started");
        }

        // HttpClient overload for SendAsync changes response behavior to fully buffered which impacts performance
        // See discussion in https://github.com/dotnet/yarp/issues/458
        if (httpClient is HttpClient)
        {
            throw new ArgumentException($"The http client must be of type HttpMessageInvoker, not HttpClient", nameof(httpClient));
        }

        // "http://a".Length = 8
        if (destinationPrefix is null || destinationPrefix.Length < 8)
        {
            throw new ArgumentException("Invalid destination prefix.", nameof(destinationPrefix));
        }

        ForwarderTelemetry.Log.ForwarderStart(destinationPrefix);

        var activityCancellationSource = ActivityCancellationTokenSource.Rent(requestConfig?.ActivityTimeout ?? DefaultTimeout, context.RequestAborted, cancellationToken);

View on GitHub (pinned to bd11867bee)

Solutions

  1. Ensure no middleware writes to the response before HttpForwarder.SendAsync runs.
  2. Short-circuit (return) after any response write instead of continuing to the forwarder.
  3. Check context.Response.HasStarted before invoking the forwarder and handle gracefully.

Example fix

// before
context.Response.StatusCode = 401;
await forwarder.SendAsync(context, address, client, reqConfig, transformer, ct); // throws
// after
context.Response.StatusCode = 401;
return; // do not forward
Defensive patterns

Strategy: validation

Validate before calling

if (RequestUtilities.IsResponseSet(context.Response))
{
    // response already started; cannot forward
    return;
}

Type guard

static bool CanStillForward(HttpContext ctx) => !RequestUtilities.IsResponseSet(ctx.Response);

Try / catch

try { await forwarder.SendAsync(context, address, client, reqConfig, transformer, ct); }
catch (InvalidOperationException ex) when (ex.Message.Contains("response has already started"))
{ logger.LogWarning("Cannot forward; response already started."); }

Prevention

When it happens

Trigger: A middleware or endpoint writes to context.Response (status, headers, body) before the forwarder runs. Calling SendAsync after an error handler or middleware that already produced output.

Common situations: Exception-handling middleware writes a response then calls the forwarder. Authentication/authorization middleware sets a 401 then continues. Custom logging middleware writes diagnostics to the response.

Related errors


AI-assisted analysis of dotnet/yarp@bd11867bee (2026-08-13). Data as JSON: /api/errors/8d1ff03f056e1ea0. Report an issue: GitHub.