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

  1. Fix the primary race: prevent AcceptUpstreamAnswer from being called after/while Close (guard at the caller with session state)
  2. Treat the joined close error as informational — the session is already terminating
  3. 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

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


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/7fd697cf4bccae38. Report an issue: GitHub.