router-for-me/CLIProxyAPI · error
SDP contains inconsistent bundled ICE credentials
Error message
SDP contains inconsistent bundled ICE credentials
What it means
Thrown while extracting ICE credentials when two m= sections in the bundled upstream answer carry different ice-ufrag/ice-pwd pairs. The proxy rewrites candidates for the whole bundle using one credential set (remote ufrag ':' local ufrag), so inconsistent credentials across media sections cannot be handled and are rejected as a protocol violation (BUNDLE requires identical credentials).
Source
Thrown at internal/client/codex/live/tcp_proxy.go:267
password := sessionPassword
if mediaPassword, ok := media.Attribute("ice-pwd"); ok {
password = mediaPassword
}
ufrag = strings.TrimSpace(ufrag)
password = strings.TrimSpace(password)
if ufrag == "" && password == "" {
continue
}
if ufrag == "" || password == "" {
return iceCredentials{}, errors.New("SDP contains incomplete ICE credentials")
}
current := iceCredentials{ufrag: ufrag, password: password}
if selected.ufrag == "" {
selected = current
continue
}
if selected != current {
return iceCredentials{}, errors.New("SDP contains inconsistent bundled ICE credentials")
}
}
if selected.ufrag == "" {
selected = iceCredentials{ufrag: strings.TrimSpace(sessionUfrag), password: strings.TrimSpace(sessionPassword)}
}
if selected.ufrag == "" || selected.password == "" {
return iceCredentials{}, errors.New("SDP is missing ICE credentials")
}
return selected, nil
}
func closeCandidateTunnels(tunnels []*tcpCandidateTunnel) error {
var closeErrors []error
for _, tunnel := range tunnels {
if errClose := tunnel.Close(); errClose != nil {
closeErrors = append(closeErrors, errClose)
}
}View on GitHub (pinned to 78f0c4079e)
Solutions
- Inspect the per-m-line a=ice-ufrag/a=ice-pwd values in the answer SDP and confirm they are identical
- Report the non-BUNDLE-compliant answer to the upstream producer
- Workaround: disable TCP proxying for the affected session
Defensive patterns
Strategy: validation
Validate before calling
// Verify identical credentials across bundled m-lines before proxying
seen := map[iceCredentials]bool{}
for _, m := range sdp.MediaDescriptions {
u, _ := m.Attribute("ice-ufrag")
p, _ := m.Attribute("ice-pwd")
if u != "" || p != "" {
seen[iceCredentials{u, p}] = true
}
}
if len(seen) > 1 {
return errors.New("non-BUNDLE-compliant answer; proxy will reject")
} Type guard
func hasConsistentBundledCredentials(pairs []iceCredentials) bool {
first := ""
for _, c := range pairs {
if c.ufrag == "" { continue }
if first == "" { first = c.ufrag + ":" + c.password; continue }
if c.ufrag+":"+c.password != first { return false }
}
return true
} Try / catch
if _, _, err := live.PrepareProxiedUpstreamAnswer(answer, offer, dialer); err != nil {
if strings.Contains(err.Error(), "inconsistent bundled ICE credentials") {
return fmt.Errorf("upstream violated BUNDLE credential rules; report to SDP producer: %w", err)
}
return err
} Prevention
- When generating multi-m-line SDP, reuse one ice-ufrag/ice-pwd pair for the bundle
- Test SDP producers with multi-media-section offers
When it happens
Trigger: Upstream answer with multiple m= lines where at least one defines credentials differing from the others, while the TCP proxy dialer is enabled.
Common situations: Upstream server bug, non-BUNDLE-compliant SDP, or SDP mangling by an intermediary that rewrites one media section's credentials.
Related errors
- SDP contains incomplete ICE credentials
- read upstream WebRTC answer ICE credentials: %w
- read upstream WebRTC offer ICE credentials: %w
- upstream WebRTC answer has no supported public TCP passive c
- upstream WebRTC TCP proxy candidate address must be an IP
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/e6ecb43a882b8829.
Report an issue: GitHub.