router-for-me/CLIProxyAPI · error
upstream WebRTC answer has no supported public TCP passive…
Error message
upstream WebRTC answer has no supported public TCP passive candidate on port 443
What it means
Thrown by prepareProxiedUpstreamAnswer when a TCP proxy dialer is configured and the upstream Codex WebRTC answer was parsed and filtered, but zero candidate lines survived. The proxy path only rewrites 'host/tcp/passive/RTP-component' candidates listening on port 443; if the upstream returned only UDP candidates, srflx/relay candidates, active TCP, or TCP on another port, plans ends up empty and the error is returned.
Solutions
- Capture the raw upstream answer SDP (log it at debug level) and inspect the a=candidate lines for tcp passive port 443 entries
- If the upstream no longer offers TCP passive on 443, disable the TCP proxy dialer for this session so the relay uses direct connectivity
- Verify the proxy dialer configuration actually points at a proxy the upstream supports — the server may serve different candidate sets depending on how the signaling connection was established
- Report upstream candidate-set changes to the maintainers if the SDP genuinely lacks supported candidates
Defensive patterns
Strategy: fallback
Try / catch
sdp, tunnels, err := live.PrepareProxiedUpstreamAnswer(answer, offer, dialer)
if err != nil && strings.Contains(err.Error(), "no supported public TCP passive candidate on port 443") {
log.Warn("upstream lacks proxiable TCP candidates; falling back to direct connection")
sdp, tunnels, err = answer, nil, nil // apply the raw answer without proxying
}
if err != nil {
return err
} Prevention
- Before enabling the TCP proxy dialer, confirm the upstream advertises tcp passive port 443 host candidates
- Log candidate lines at debug level in new deployments to detect upstream ICE strategy changes
- Implement a fallback path that applies the unproxied answer when no proxyable candidate exists
When it happens
Trigger: Configuring the live media relay to dial the upstream through an HTTP CONNECT/SOCKS TCP proxy (proxyDialer != nil) while the upstream server's SDP contains no a=candidate ... tcp ... passive ... 443 host line.
Common situations: Upstream Codex endpoint changes its ICE strategy (e.g. prefers UDP-only or moves off port 443); a proxy environment where the server behind it advertises non-standard candidates; region-specific infrastructure differences.
Related errors
- upstream WebRTC TCP proxy candidate address must be an IP
- upstream WebRTC TCP proxy candidate is malformed
- Codex live TCP proxy listener returned an invalid address
- downstream WebRTC answer is empty
- marshal proxied upstream WebRTC answer
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/b603531a20d3a52f.
Report an issue: GitHub.
Appendix: source
Thrown at internal/client/codex/live/tcp_proxy.go:147
plan, keep, errCandidate := proxiedTCPCandidatePlan(attribute.Value)
if errCandidate != nil {
return "", nil, errCandidate
}
if !keep {
continue
}
if len(plans) >= maxProxiedTCPCandidates {
return "", nil, fmt.Errorf("upstream WebRTC answer exceeds the %d TCP candidate proxy limit", maxProxiedTCPCandidates)
}
plan.mediaIndex = mediaIndex
plan.attributeIndex = len(filtered)
filtered = append(filtered, attribute)
plans = append(plans, plan)
}
media.Attributes = filtered
}
if len(plans) == 0 {
return "", nil, errors.New("upstream WebRTC answer has no supported public TCP passive candidate on port 443")
}
expectedUser := remoteCredentials.ufrag + ":" + localCredentials.ufrag
tunnels := make([]*tcpCandidateTunnel, 0, len(plans))
closeTunnels := func() {
for _, tunnel := range tunnels {
if errClose := tunnel.Close(); errClose != nil {
log.WithError(errClose).Debug("codex live TCP proxy: close candidate tunnel after setup error")
}
}
}
for _, plan := range plans {
tunnel, errTunnel := newTCPCandidateTunnel(plan.target, dialer, expectedUser, remoteCredentials.password)
if errTunnel != nil {
closeTunnels()
return "", nil, errTunnel
}
tunnels = append(tunnels, tunnel)View on GitHub (pinned to 78f0c4079e)