From f97c7bd3cc9d99b297c77e592c2b8f406bedc6ba Mon Sep 17 00:00:00 2001 From: Justin Reid Date: Fri, 6 Mar 2026 16:11:18 -0500 Subject: [PATCH] Fix grpc-status sent as initial header instead of trailer. When a gRPC response includes a body, grpc-status must be sent as an HTTP/2 trailing HEADERS frame. However, invoke_service was adding grpc-status to response.headers before send_response called trailer!, causing it to be included in the initial headers instead of trailers. Call trailer! at the start of invoke_service to mark the header position. Since trailer! uses ||= for @tail, the subsequent call in send_response is a no-op, and any headers added after our call (including grpc-status) are correctly positioned as trailers. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/async/grpc/dispatcher.rb | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/async/grpc/dispatcher.rb b/lib/async/grpc/dispatcher.rb index 848611f..585ea3d 100644 --- a/lib/async/grpc/dispatcher.rb +++ b/lib/async/grpc/dispatcher.rb @@ -45,17 +45,26 @@ def register(service, name: service.service_name) protected def invoke_service(service, handler_method, input, output, call) + # Mark the current header position so that any headers added after + # this point (e.g. grpc-status) are sent as HTTP/2 trailing headers + # rather than initial headers. The HTTP/2 server calls trailer! via + # send_response, but that happens after dispatch returns the response. + # By calling trailer! here first (@tail is set via ||= so the later + # call is a no-op), we ensure grpc-status ends up in the trailer + # portion of the headers. + call.response&.headers&.trailer! + begin service.send(handler_method, input, output, call) ensure # Close input stream: input.close - + # Close output stream: output.close_write unless output.closed? end - - # Mark trailers and add status (if not already set by handler): + + # Add OK status as a trailer (if not already set by handler): if headers = call.response&.headers # Only add OK status if grpc-status hasn't been set by the handler: unless headers["grpc-status"]