{"record":{"id":"4cbe68e950713b8b","repo":"router-for-me/CLIProxyAPI","slug":"add-downstream-audio-track-w","errorCode":null,"errorMessage":"add downstream audio track: %w","messagePattern":"add downstream audio track: %w","errorType":"http","errorClass":null,"httpStatus":502,"severity":"error","filePath":"internal/client/codex/live/media.go","lineNumber":337,"sourceCode":"\tlog.WithFields(session.logFields(\"session\")).Info(\"codex live WebRTC media session created\")\n\n\tif errRemote := downstream.SetRemoteDescription(webrtc.SessionDescription{\n\t\tType: webrtc.SDPTypeOffer,\n\t\tSDP:  clientOffer,\n\t}); errRemote != nil {\n\t\t_ = session.Close()\n\t\treturn nil, \"\", fmt.Errorf(\"set downstream WebRTC offer: %w\", errRemote)\n\t}\n\n\ttoDesktop, errTrack := webrtc.NewTrackLocalStaticRTP(opusCodec, \"audio\", \"codex-live\")\n\tif errTrack != nil {\n\t\t_ = session.Close()\n\t\treturn nil, \"\", fmt.Errorf(\"create downstream audio track: %w\", errTrack)\n\t}\n\tdownstreamSender, errTrack := downstream.AddTrack(toDesktop)\n\tif errTrack != nil {\n\t\t_ = session.Close()\n\t\treturn nil, \"\", fmt.Errorf(\"add downstream audio track: %w\", errTrack)\n\t}\n\tgo drainRTCP(\"downstream\", downstreamSender, session.done)\n\n\ttoOpenAI, errTrack := webrtc.NewTrackLocalStaticRTP(opusCodec, \"audio\", \"codex-live\")\n\tif errTrack != nil {\n\t\t_ = session.Close()\n\t\treturn nil, \"\", fmt.Errorf(\"create upstream audio track: %w\", errTrack)\n\t}\n\tupstreamSender, errTrack := upstream.AddTrack(toOpenAI)\n\tif errTrack != nil {\n\t\t_ = session.Close()\n\t\treturn nil, \"\", fmt.Errorf(\"add upstream audio track: %w\", errTrack)\n\t}\n\tgo drainRTCP(\"upstream\", upstreamSender, session.done)\n\n\tdownstream.OnTrack(func(track *webrtc.TrackRemote, _ *webrtc.RTPReceiver) {\n\t\tif !strings.EqualFold(track.Codec().MimeType, webrtc.MimeTypeOpus) {\n\t\t\treturn","sourceCodeStart":319,"sourceCodeEnd":355,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/client/codex/live/media.go#L319-L355","documentation":"Thrown when downstream.AddTrack(toDesktop) fails while attaching the client-facing audio track. Pion AddTrack errors when the track's codec is not negotiated in the remote offer (the client offer lacks an audio m-line compatible with Opus 111) or when SDP negotiation state makes the direction invalid. The session is closed on failure.","triggerScenarios":"Client offer contains no audio section, an audio section with an incompatible codec (e.g. PCMU-only), or recvonly audio incompatible with the sender direction AddTrack implies.","commonSituations":"Clients that connect in listen-only or data-channel-only mode, or offer a non-Opus audio codec, so AddTrack cannot bind the Opus sender.","solutions":["Require client offers to include a sendrecv/recvonly audio m-line offering Opus","If data-channel-only sessions must be supported, skip track creation rather than failing the session","Inspect the client offer SDP for 'm=audio' and 'opus' before session setup"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"func offerSupportsOpusAudio(offer string) bool {\n\tfor _, line := range strings.Split(offer, \"\\n\") {\n\t\tif strings.HasPrefix(strings.TrimSpace(line), \"m=audio\") {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}","typeGuard":null,"tryCatchPattern":"if _, _, err := relay.NewSession(ctx, offer, route); err != nil {\n\tif strings.Contains(err.Error(), \"add downstream audio track\") {\n\t\trespondUnsupportedMedia(w) // 415: offer lacks negotiable Opus audio\n\t\treturn\n\t}\n}","preventionTips":["Document that clients must offer Opus audio","Reject audio-less offers early with 415 instead of failing mid-setup","Cover this path with a unit test using a video-only offer"],"tags":["webrtc","audio","negotiation","opus","track"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}