router-for-me/CLIProxyAPI · warning
close TCP candidate tunnels: %w
Error message
close TCP candidate tunnels: %w
What it means
A secondary, joined error (errors.Join) thrown when closing the TCP candidate tunnels fails after the session was already detected closed while configuring the TCP proxy. The primary error is 'Codex live media session closed while configuring TCP proxy'; this wrap only reports that the cleanup of the local TCP listeners (tunnels toward the proxy) also failed. Always accompanied by the primary closed-session error.
Source
Thrown at internal/client/codex/live/media.go:422
}
func (s *pionMediaSession) AcceptUpstreamAnswer(ctx context.Context, upstreamAnswer string) (string, error) {
if s == nil || s.upstream == nil || s.downstream == nil {
return "", errors.New("Codex live media session unavailable")
}
answerToApply := upstreamAnswer
if s.proxyDialer != nil {
rewrittenAnswer, tunnels, errProxy := prepareProxiedUpstreamAnswer(upstreamAnswer, s.localOffer, s.proxyDialer)
if errProxy != nil {
return "", errProxy
}
for _, tunnel := range tunnels {
tunnel.setForwardingStartedHandler(s.logForwardingStarted)
}
if !s.installCandidateTunnels(tunnels) {
errClosed := errors.New("Codex live media session closed while configuring TCP proxy")
if errClose := closeCandidateTunnels(tunnels); errClose != nil {
return "", errors.Join(errClosed, fmt.Errorf("close TCP candidate tunnels: %w", errClose))
}
return "", errClosed
}
answerToApply = rewrittenAnswer
}
if errRemote := s.upstream.SetRemoteDescription(webrtc.SessionDescription{
Type: webrtc.SDPTypeAnswer,
SDP: answerToApply,
}); errRemote != nil {
errSetRemote := fmt.Errorf("set upstream WebRTC answer: %w", errRemote)
if errClose := s.closeCandidateTunnels(); errClose != nil {
return "", errors.Join(errSetRemote, fmt.Errorf("close TCP candidate tunnels: %w", errClose))
}
return "", errSetRemote
}
gatherComplete := webrtc.GatheringCompletePromise(s.downstream)
answer, errAnswer := s.downstream.CreateAnswer(nil)
if errAnswer != nil {View on GitHub (pinned to 78f0c4079e)
Solutions
- Fix the primary race: prevent AcceptUpstreamAnswer from being called after/while Close (guard at the caller with session state)
- Treat the joined close error as informational — the session is already terminating
- Serialize Close and AcceptUpstreamAnswer with the session mutex if adding lifecycle guards
Defensive patterns
Strategy: try-catch
Try / catch
if _, err := session.AcceptUpstreamAnswer(ctx, answer); err != nil {
if strings.Contains(err.Error(), "session closed while configuring TCP proxy") {
// benign teardown race; joined close error is diagnostic only
log.WithError(err).Debug("codex live: answer raced session close")
return
}
log.WithError(err).Error("codex live: apply upstream answer failed")
} Prevention
- Do not call AcceptUpstreamAnswer after triggering Close (guard with a session-state flag)
- Log joined errors at debug — the session is already terminating
- Fix the primary race, not the secondary close error
When it happens
Trigger: Session.Close() runs concurrently with AcceptUpstreamAnswer while tunnels were prepared; installCandidateTunnels returns false because s.tunnels state shows the session closed, and then closeCandidateTunnels hits already-closed listeners.
Common situations: Client disconnect racing the upstream answer application in proxied mode; benign in effect (everything is being torn down anyway) but noisy in logs.
Related errors
- set upstream WebRTC answer: %w close TCP candidate tunnels:
- create upstream PeerConnection: %w
- create upstream DataChannel: %w
- create upstream WebRTC offer: %w
- set upstream WebRTC offer: %w
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/7fd697cf4bccae38.
Report an issue: GitHub.