@@ -182,6 +182,75 @@ defmodule CodexPoolerWeb.Runtime.BackendCodexWebsocketOwnerForwardingTest do
182182 end
183183 end
184184
185+ test "owner-forwarded upstream close before terminal persists safe transport metadata" do
186+ upstream =
187+ start_upstream (
188+ FakeUpstream . websocket_sse_then_close (
189+ [
190+ { "response.output_text.delta" ,
191+ % {
192+ "type" => "response.output_text.delta" ,
193+ "response_id" => "resp_owner_transport_failure" ,
194+ "output_index" => 0 ,
195+ "content_index" => 0 ,
196+ "delta" => @ sentinel
197+ } }
198+ ] ,
199+ reason: "owner upstream close reason sentinel"
200+ )
201+ )
202+
203+ setup = gateway_setup ( upstream )
204+ { :ok , auth } = Access . authenticate_authorization_header ( setup . authorization )
205+ { :ok , state } = owner_socket ( auth , "ws-owner-transport-failure" , "owner-transport-failure" )
206+
207+ try do
208+ payload =
209+ websocket_payload ( setup , "owner forwarded transport failure" , % {
210+ "request_id" => "ws-owner-transport-failure"
211+ } )
212+
213+ assert { :ok , state } = CodexResponsesSocket . handle_in ( { payload , [ opcode: :text ] } , state )
214+
215+ assert { :push , { :text , partial_frame } , state } = receive_owner_socket_push ( state )
216+
217+ assert % { "type" => "response.output_text.delta" , "delta" => @ sentinel } =
218+ Jason . decode! ( partial_frame )
219+
220+ assert { :push , { :text , error_frame } , _state } = receive_socket_done ( state )
221+
222+ assert % { "type" => "error" , "error" => % { "code" => "upstream_request_failed" } } =
223+ Jason . decode! ( error_frame )
224+
225+ assert [ request_log ] = request_logs ( setup . pool . id )
226+ assert request_log . status == "failed"
227+ assert request_log . transport == "websocket"
228+ assert request_log . last_error_code == "upstream_stream_error"
229+
230+ assert [ attempt ] = Repo . all ( from ( a in Attempt , where: a . request_id == ^ request_log . id ) )
231+ assert attempt . status == "failed"
232+
233+ assert attempt . response_metadata [ "transport_failure" ] == % {
234+ "phase" => "upstream_close" ,
235+ "pre_visible_output" => false ,
236+ "reason" => "upstream_websocket_closed_before_terminal" ,
237+ "reason_class" => "upstream_websocket_closed_before_terminal" ,
238+ "terminal_seen" => false ,
239+ "text_frame_count" => 1
240+ }
241+
242+ metadata_text = inspect ( attempt . response_metadata )
243+ refute metadata_text =~ @ sentinel
244+ refute metadata_text =~ "owner upstream close reason sentinel"
245+ refute metadata_text =~ setup . authorization
246+ refute metadata_text =~ setup . raw_key
247+ refute metadata_text =~ "Bearer "
248+ refute metadata_text =~ "upstream-token"
249+ after
250+ CodexResponsesSocket . terminate ( :closed , state )
251+ end
252+ end
253+
185254 @ tag :feature_websocket_terminal_auth_refresh
186255 test "owner-forwarded websocket terminal auth refresh retries through the same owner session" do
187256 upstream =
0 commit comments