{"record":{"id":"8b74f86c91f4b56d","repo":"router-for-me/CLIProxyAPI","slug":"parse-upstream-webrtc-candidate-w","errorCode":null,"errorMessage":"parse upstream WebRTC candidate: %w","messagePattern":"parse upstream WebRTC candidate: %w","errorType":"http","errorClass":null,"httpStatus":502,"severity":"error","filePath":"internal/client/codex/live/tcp_proxy.go","lineNumber":189,"sourceCode":"\t\tfields := append([]string(nil), plan.fields...)\n\t\tfields[4] = listenerAddress.IP.String()\n\t\tfields[5] = strconv.Itoa(listenerAddress.Port)\n\t\tremoteDescription.MediaDescriptions[plan.mediaIndex].Attributes[plan.attributeIndex].Value = strings.Join(fields, \" \")\n\t}\n\n\trewritten, errMarshal := remoteDescription.Marshal()\n\tif errMarshal != nil {\n\t\tcloseTunnels()\n\t\treturn \"\", nil, fmt.Errorf(\"marshal proxied upstream WebRTC answer: %w\", errMarshal)\n\t}\n\treturn string(rewritten), tunnels, nil\n}\n\nfunc proxiedTCPCandidatePlan(rawCandidate string) (tcpCandidatePlan, bool, error) {\n\ttrimmed := strings.TrimSpace(rawCandidate)\n\tcandidate, errCandidate := ice.UnmarshalCandidate(trimmed)\n\tif errCandidate != nil {\n\t\treturn tcpCandidatePlan{}, false, fmt.Errorf(\"parse upstream WebRTC candidate: %w\", errCandidate)\n\t}\n\tif candidate.NetworkType() != ice.NetworkTypeTCP4 && candidate.NetworkType() != ice.NetworkTypeTCP6 {\n\t\treturn tcpCandidatePlan{}, false, nil\n\t}\n\tif candidate.TCPType() != ice.TCPTypePassive {\n\t\treturn tcpCandidatePlan{}, false, nil\n\t}\n\tif candidate.Component() != uint16(ice.ComponentRTP) || candidate.Type() != ice.CandidateTypeHost {\n\t\treturn tcpCandidatePlan{}, false, nil\n\t}\n\tif candidate.Port() != 443 {\n\t\treturn tcpCandidatePlan{}, false, fmt.Errorf(\"upstream WebRTC TCP proxy candidate uses disallowed port %d\", candidate.Port())\n\t}\n\taddress, errAddress := netip.ParseAddr(candidate.Address())\n\tif errAddress != nil {\n\t\treturn tcpCandidatePlan{}, false, errors.New(\"upstream WebRTC TCP proxy candidate address must be an IP\")\n\t}\n\taddress = address.Unmap()","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/client/codex/live/tcp_proxy.go#L171-L207","documentation":"Each a=candidate line in the answer is parsed with pion/ice's UnmarshalCandidate. This error means at least one candidate line is syntactically invalid under RFC 8445 (wrong field count, unknown foundation quirks, bad priority, etc.), and the proxy treats a single bad candidate as fatal rather than skipping it.","triggerScenarios":"proxiedTCPCandidatePlan(attribute.Value) is called for every candidate attribute; ice.UnmarshalCandidate fails on one — e.g. a candidate with fewer than the required fields, a non-numeric component, or an extension format pion/ice rejects.","commonSituations":"Upstream emits a candidate dialect pion/ice does not accept (e.g. odd extensions or 'a=candidate:' prefix handling); SDP was mutated/truncated in transit; older pion/ice version lacking newer candidate features.","solutions":["Log the exact candidate string that failed and validate its field layout against RFC 8445.","Upgrade github.com/pion/ice — candidate parsing gains formats across releases.","If the upstream format is stable and merely unsupported, pre-normalize the candidate string (strip prefixes, fix spacing) before UnmarshalCandidate."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Skip candidates that are obviously malformed before calling the proxy\nfunc isPlausibleCandidate(line string) bool {\n\tf := strings.Fields(strings.TrimPrefix(line, \"a=candidate:\"))\n\treturn len(f) >= 8\n}","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"parse upstream WebRTC candidate\") {\n\tlog.WithError(err).Warn(\"skipping session: upstream emitted unparsable candidate\")\n\treturn err\n}","preventionTips":["Keep pion/ice current; candidate grammar support improves between releases.","Capture the failing candidate string in logs to distinguish upstream change vs corruption."],"tags":["webrtc","ice","candidate","parsing","codex-live"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}