{"record":{"id":"b9a8e8403bfc8e02","repo":"router-for-me/CLIProxyAPI","slug":"set-upstream-webrtc-answer-w-close-tcp-candidate","errorCode":null,"errorMessage":"set upstream WebRTC answer: %w\nclose TCP candidate tunnels: %w","messagePattern":"set upstream WebRTC answer: %w\nclose TCP candidate tunnels: %w","errorType":"http","errorClass":null,"httpStatus":502,"severity":"error","filePath":"internal/client/codex/live/media.go","lineNumber":434,"sourceCode":"\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)\n\tanswer, errAnswer := s.downstream.CreateAnswer(nil)\n\tif errAnswer != nil {\n\t\treturn \"\", fmt.Errorf(\"create downstream WebRTC answer: %w\", errAnswer)\n\t}\n\tif errLocal := s.downstream.SetLocalDescription(answer); errLocal != nil {\n\t\treturn \"\", fmt.Errorf(\"set downstream WebRTC answer: %w\", errLocal)\n\t}\n\tselect {\n\tcase <-gatherComplete:\n\tcase <-ctx.Done():\n\t\treturn \"\", fmt.Errorf(\"gather downstream WebRTC candidates: %w\", ctx.Err())\n\t}\n\tlocalDescription := s.downstream.LocalDescription()\n\tif localDescription == nil || strings.TrimSpace(localDescription.SDP) == \"\" {","sourceCodeStart":416,"sourceCodeEnd":452,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/client/codex/live/media.go#L416-L452","documentation":"The errors.Join composite returned when SetRemoteDescription of the upstream answer fails AND the subsequent rollback (closing the installed TCP candidate tunnels) also fails. The first '%w' is the upstream answer failure (see 335); the second is the tunnel-close failure. Both errors are preserved so the real root cause (usually the SDP mismatch) is readable from the joined chain.","triggerScenarios":"Same as 335 on the proxied path (tunnels were installed), plus closeCandidateTunnels erroring — typically because the session's Close already ran and the tunnels were already closed.","commonSituations":"Proxied deployment where the answer application fails while a concurrent teardown closes tunnels; inspect with errors.Is/As on the joined chain.","solutions":["Unwrap with errors.Is to identify the primary cause (the answer SDP error) and fix per error 335","Prevent the teardown race that double-closes tunnels","Report the joined error verbatim in logs — do not collapse it, the second error is diagnostic"],"exampleFix":"// before\nif err := session.AcceptUpstreamAnswer(ctx, answer); err != nil {\n\tlog.Errorf(\"answer failed: %v\", err)\n}\n\n// after\nif err := session.AcceptUpstreamAnswer(ctx, answer); err != nil {\n\tvar sdpErr *webrtc.InvalidStateError\n\tlog.Errorf(\"answer failed: %v (sdp-state=%v)\", err, errors.As(err, &sdpErr))\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if _, err := session.AcceptUpstreamAnswer(ctx, answer); err != nil {\n\tfor unwindErr := err; unwindErr != nil; {\n\t\tif joined, ok := unwindErr.(interface{ Unwrap() []error }); ok {\n\t\t\tfor _, inner := range joined.Unwrap() {\n\t\t\t\tlog.WithError(inner).Debug(\"joined failure component\")\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\tunwindErr = errors.Unwrap(unwindErr)\n\t}\n}","preventionTips":["Preserve the joined chain — each component localizes a different failure (SDP vs tunnel close)","Never retry on a session that failed here; create a new session instead","Log both components with errors.Is/As classification"],"tags":["webrtc","sdp","errors-join","tcp-tunnel","cleanup"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}