router-for-me/CLIProxyAPI · error
parse upstream WebRTC offer for TCP proxy: %w
Error message
parse upstream WebRTC offer for TCP proxy: %w
What it means
Same rewrite pipeline as the answer parse, but for the LOCAL offer SDP that this client generated. If the local offer cannot be parsed back, the proxy cannot read the local ICE credentials needed to authenticate tunnel connections, so it fails before dialing anything.
Source
Thrown at internal/client/codex/live/tcp_proxy.go:101
type tcpCandidatePlan struct {
mediaIndex int
attributeIndex int
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 {View on GitHub (pinned to 78f0c4079e)
Solutions
- Check that s.localOffer is set from the actual local peer connection's local description before the answer arrives.
- Log len(localOffer) and its first line to verify it starts with 'v=0'.
- Fix the session lifecycle bug that leaves the offer empty or stale.
Example fix
// before
rewrittenAnswer, tunnels, errProxy := prepareProxiedUpstreamAnswer(upstreamAnswer, s.localOffer, s.proxyDialer)
// after: fail fast on an uninitialized offer
if strings.TrimSpace(s.localOffer) == "" {
return errors.New("local offer is not initialized before upstream answer")
}
rewrittenAnswer, tunnels, errProxy := prepareProxiedUpstreamAnswer(upstreamAnswer, s.localOffer, s.proxyDialer) Defensive patterns
Strategy: validation
Validate before calling
if strings.TrimSpace(s.localOffer) == "" || !strings.HasPrefix(strings.TrimSpace(s.localOffer), "v=") {
return errors.New("local offer missing or not SDP; cannot prepare TCP proxy")
} Prevention
- Initialize and store localDescription.SDP before subscribing to answer events.
- Add a session-state unit test that asserts the offer is non-empty at answer time.
When it happens
Trigger: prepareProxiedUpstreamAnswer is called with a localOffer string that fails sdp.SessionDescription.UnmarshalString — typically an empty string, a truncated offer, or a non-SDP value passed by the caller (media session stored something other than the raw SDP).
Common situations: Session state was not initialized before the upstream answer arrived (localOffer empty); the offer was serialized/deserialized through a layer that escaped or truncated it; race where the session was reset mid-handshake.
Related errors
- read upstream WebRTC offer ICE credentials: %w
- parse upstream WebRTC answer for TCP proxy: %w
- read upstream WebRTC answer ICE credentials: %w
- marshal proxied upstream WebRTC answer: %w
- upstream WebRTC answer has no supported public TCP passive c
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/f5b95e1bd79d09fe.
Report an issue: GitHub.