router-for-me/CLIProxyAPI · error
read upstream WebRTC answer ICE credentials: %w
Error message
read upstream WebRTC answer ICE credentials: %w
What it means
After parsing the upstream answer, the proxy extracts the ICE ufrag/password (bundledICECredentials) to validate incoming TCP tunnel connections. If the answer's media sections lack ice-ufrag/ice-pwd attributes (or they are malformed), credential extraction fails and the proxy aborts.
Source
Thrown at internal/client/codex/live/tcp_proxy.go:105
fields []string
target netip.AddrPort
}
func prepareProxiedUpstreamAnswer(answer, localOffer string, dialer proxy.ContextDialer) (string, []*tcpCandidateTunnel, error) {
if dialer == nil {
return "", nil, errors.New("Codex live TCP proxy dialer is unavailable")
}
var remoteDescription sdp.SessionDescription
if errUnmarshal := remoteDescription.UnmarshalString(answer); errUnmarshal != nil {
return "", nil, fmt.Errorf("parse upstream WebRTC answer for TCP proxy: %w", errUnmarshal)
}
var localDescription sdp.SessionDescription
if errUnmarshal := localDescription.UnmarshalString(localOffer); errUnmarshal != nil {
return "", nil, fmt.Errorf("parse upstream WebRTC offer for TCP proxy: %w", errUnmarshal)
}
remoteCredentials, errCredentials := bundledICECredentials(&remoteDescription)
if errCredentials != nil {
return "", nil, fmt.Errorf("read upstream WebRTC answer ICE credentials: %w", errCredentials)
}
localCredentials, errCredentials := bundledICECredentials(&localDescription)
if errCredentials != nil {
return "", nil, fmt.Errorf("read upstream WebRTC offer ICE credentials: %w", errCredentials)
}
plans := make([]tcpCandidatePlan, 0, 4)
candidateCount := 0
for mediaIndex, media := range remoteDescription.MediaDescriptions {
if media == nil {
continue
}
filtered := make([]sdp.Attribute, 0, len(media.Attributes))
for attributeIndex := range media.Attributes {
attribute := media.Attributes[attributeIndex]
if !attribute.IsICECandidate() {
filtered = append(filtered, attribute)
continueView on GitHub (pinned to 78f0c4079e)
Solutions
- Dump the answer SDP and check each m= section for a=ice-ufrag and a=ice-pwd.
- If upstream now uses trickle ICE or session-level-only credentials, extend bundledICECredentials to fall back to session-level attributes.
- If the upstream regression is real, pin/roll back to the upstream version that emits full credentials.
Defensive patterns
Strategy: try-catch
Validate before calling
// Check the answer carries ICE credentials before proxying
hasICE := false
for _, m := range parsed.MediaDescriptions {
for _, a := range m.Attributes {
if a.Key == "ice-ufrag" || a.Key == "ice-pwd" {
hasICE = true
}
}
}
if !hasICE { /* skip TCP proxying, use default path */ } Try / catch
if _, _, err := prepareProxiedUpstreamAnswer(answer, offer, dialer); err != nil {
if strings.Contains(err.Error(), "ICE credentials") {
log.Warn("answer lacks ICE credentials; falling back to direct connection")
return useDirectPath(answer)
}
} Prevention
- Monitor upstream SDP shape changes (trickle ICE) in canary sessions.
- Keep a fixture of a known-good answer in tests to detect regressions early.
When it happens
Trigger: The upstream answer SDP parses but contains no session-level or media-level a=ice-ufrag / a=ice-pwd attributes — e.g. a trickle-ICE answer that defers credentials, or an m-line with missing ICE attributes.
Common situations: Upstream changed to trickle ICE or a different SDP shape; attributes present only at session level while the parser expects them per media section (or vice versa); a stripped-down test SDP used in development.
Related errors
- read upstream WebRTC offer ICE credentials: %w
- SDP contains incomplete ICE credentials
- SDP contains inconsistent bundled ICE credentials
- parse upstream WebRTC answer for TCP proxy: %w
- parse upstream WebRTC offer for TCP proxy: %w
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/cf9021c37d309340.
Report an issue: GitHub.