{"record":{"id":"c751ee9ed9f52152","repo":"router-for-me/CLIProxyAPI","slug":"gather-upstream-webrtc-candidates-w","errorCode":null,"errorMessage":"gather upstream WebRTC candidates: %w","messagePattern":"gather upstream WebRTC candidates: %w","errorType":"http","errorClass":null,"httpStatus":502,"severity":"error","filePath":"internal/client/codex/live/media.go","lineNumber":395,"sourceCode":"\t\treturn nil, \"\", fmt.Errorf(\"create upstream DataChannel: %w\", errChannel)\n\t}\n\tsession.bridge.attachUpstream(upstreamChannel)\n\n\tgatherComplete := webrtc.GatheringCompletePromise(upstream)\n\toffer, errOffer := upstream.CreateOffer(nil)\n\tif errOffer != nil {\n\t\t_ = session.Close()\n\t\treturn nil, \"\", fmt.Errorf(\"create upstream WebRTC offer: %w\", errOffer)\n\t}\n\tif errLocal := upstream.SetLocalDescription(offer); errLocal != nil {\n\t\t_ = session.Close()\n\t\treturn nil, \"\", fmt.Errorf(\"set upstream WebRTC offer: %w\", errLocal)\n\t}\n\tselect {\n\tcase <-gatherComplete:\n\tcase <-ctx.Done():\n\t\t_ = session.Close()\n\t\treturn nil, \"\", fmt.Errorf(\"gather upstream WebRTC candidates: %w\", ctx.Err())\n\t}\n\tlocalDescription := upstream.LocalDescription()\n\tif localDescription == nil || strings.TrimSpace(localDescription.SDP) == \"\" {\n\t\t_ = session.Close()\n\t\treturn nil, \"\", errors.New(\"upstream WebRTC offer is empty\")\n\t}\n\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 {","sourceCodeStart":377,"sourceCodeEnd":413,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/client/codex/live/media.go#L377-L413","documentation":"Thrown when the context is cancelled while waiting for ICE candidate gathering to complete on the upstream PC (select on gatherComplete vs ctx.Done()). It wraps the context error (context.Canceled or context.DeadlineExceeded), meaning the caller abandoned the session before a full set of upstream candidates was collected.","triggerScenarios":"HTTP request context cancelled (client disconnected), a short deadline on the session-establishment context, or gathering stalled because no network interfaces/candidates are available (e.g. fully firewalled host with no loopback route on the proxy API).","commonSituations":"Client cancelling the WebRTC offer request mid-negotiation; aggressive per-request timeouts in front of the media endpoint; a container with restricted networking where ICE gathering hangs.","solutions":["Increase or remove the deadline on the context passed to NewSession/AcceptUpstreamAnswer","Keep the client connected during negotiation (gathering usually completes in <1s with host candidates)","For proxied deployments verify loopback networking works inside the container (proxyUpstreamAPI is loopback-only)","Check relayConfig.PublicIP / port range so host candidates are generatable"],"exampleFix":"// before\nctx, cancel := context.WithTimeout(ctx, 500*time.Millisecond)\n\n// after\nctx, cancel := context.WithTimeout(ctx, 10*time.Second)","handlingStrategy":"validation","validationCode":"if err := ctx.Err(); err != nil {\n\treturn nil, \"\", err // do not start gathering with an already-cancelled context\n}","typeGuard":null,"tryCatchPattern":"select {\ncase <-gatherComplete:\ncase <-ctx.Done():\n\tif errors.Is(ctx.Err(), context.DeadlineExceeded) {\n\t\tlog.Warn(\"ICE gathering exceeded deadline; consider raising context timeout\")\n\t}\n\treturn nil, \"\", fmt.Errorf(\"gather upstream WebRTC candidates: %w\", ctx.Err())\n}","preventionTips":["Pass a context with generous deadline (>=10s) to NewSession/AcceptUpstreamAnswer","Verify container networking (loopback, UDP) so gathering cannot stall","Cancel only when the client is truly gone — cancellation here wastes a fully set-up session"],"tags":["webrtc","ice","context","timeout","gathering"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}