{"record":{"id":"045f2d7893909f7d","repo":"router-for-me/CLIProxyAPI","slug":"codex-live-media-session-closed-while-configuring","errorCode":null,"errorMessage":"Codex live media session closed while configuring TCP proxy","messagePattern":"Codex live media session closed while configuring TCP proxy","errorType":"http","errorClass":null,"httpStatus":502,"severity":"warning","filePath":"internal/client/codex/live/media.go","lineNumber":420,"sourceCode":"\tsession.localOffer = localDescription.SDP\n\treturn session, localDescription.SDP, nil\n}\n\nfunc (s *pionMediaSession) AcceptUpstreamAnswer(ctx context.Context, upstreamAnswer string) (string, error) {\n\tif s == nil || s.upstream == nil || s.downstream == nil {\n\t\treturn \"\", errors.New(\"Codex live media session unavailable\")\n\t}\n\tanswerToApply := upstreamAnswer\n\tif s.proxyDialer != nil {\n\t\trewrittenAnswer, tunnels, errProxy := prepareProxiedUpstreamAnswer(upstreamAnswer, s.localOffer, s.proxyDialer)\n\t\tif errProxy != nil {\n\t\t\treturn \"\", errProxy\n\t\t}\n\t\tfor _, tunnel := range tunnels {\n\t\t\ttunnel.setForwardingStartedHandler(s.logForwardingStarted)\n\t\t}\n\t\tif !s.installCandidateTunnels(tunnels) {\n\t\t\terrClosed := errors.New(\"Codex live media session closed while configuring TCP proxy\")\n\t\t\tif errClose := closeCandidateTunnels(tunnels); errClose != nil {\n\t\t\t\treturn \"\", errors.Join(errClosed, fmt.Errorf(\"close TCP candidate tunnels: %w\", errClose))\n\t\t\t}\n\t\t\treturn \"\", errClosed\n\t\t}\n\t\tanswerToApply = rewrittenAnswer\n\t}\n\tif errRemote := s.upstream.SetRemoteDescription(webrtc.SessionDescription{\n\t\tType: webrtc.SDPTypeAnswer,\n\t\tSDP:  answerToApply,\n\t}); errRemote != nil {\n\t\terrSetRemote := fmt.Errorf(\"set upstream WebRTC answer: %w\", errRemote)\n\t\tif errClose := s.closeCandidateTunnels(); errClose != nil {\n\t\t\treturn \"\", errors.Join(errSetRemote, fmt.Errorf(\"close TCP candidate tunnels: %w\", errClose))\n\t\t}\n\t\treturn \"\", errSetRemote\n\t}\n\tgatherComplete := webrtc.GatheringCompletePromise(s.downstream)","sourceCodeStart":402,"sourceCodeEnd":438,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/client/codex/live/media.go#L402-L438","documentation":"Thrown while applying an upstream WebRTC answer through a TCP proxy: installCandidateTunnels() detected that the session's done channel was already closed, meaning the media session was torn down concurrently. All freshly created TCP candidate tunnels are closed again and the error is returned from AcceptUpstreamAnswer. It is a lifecycle race between session shutdown and answer negotiation, not a protocol failure.","triggerScenarios":"Calling AcceptUpstreamAnswer on a pionMediaSession whose Close() already ran (client disconnected, relay shutdown, or max-session eviction), while a proxyDialer is configured so prepareProxiedUpstreamAnswer built candidate tunnels.","commonSituations":"Client cancels the live session right as the server answer arrives; the relay evicts the session due to max-sessions pressure; a shutdown path races normal negotiation during hot config reload.","solutions":["Treat as a benign lifecycle race: check whether the session was intentionally closed (client disconnect, shutdown) before investigating further","Ensure your caller does not invoke AcceptUpstreamAnswer after Close() — serialize negotiation and teardown with the session mutex/done channel","If unexpected, audit what closed the session: look for max-sessions limits in codex.live-media-relay config or watchdog timeouts"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Go: check session liveness before applying the answer\nselect {\ncase <-session.Done():\n    return errors.New(\"session already closed; skip answer\")\ndefault:\n}\n_, err := session.AcceptUpstreamAnswer(ctx, answerSDP)","typeGuard":null,"tryCatchPattern":"if _, err := session.AcceptUpstreamAnswer(ctx, answerSDP); err != nil {\n    if strings.Contains(err.Error(), \"closed while configuring TCP proxy\") {\n        return nil // benign teardown race; nothing to undo\n    }\n    return err\n}","preventionTips":["Serialize Close() and AcceptUpstreamAnswer() behind the session lifecycle (done channel / mutex)","Cancel the answer-negotiation context whenever the client disconnects","Monitor for this error at warning level; a rising rate indicates sessions being evicted mid-negotiation"],"tags":["webrtc","codex","media-relay","race-condition","lifecycle"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}