router-for-me/CLIProxyAPI · error

listen for Codex live TCP proxy candidate: %w

Error message

listen for Codex live TCP proxy candidate: %w

What it means

For each proxied candidate the proxy opens a loopback TCP listener (127.0.0.1:0 or [::1]:0) that the local ICE agent connects to. This error means net.Listen on loopback failed — the OS refused a new listener, typically due to file-descriptor exhaustion or loopback networking being unavailable in the runtime environment.

Source

Thrown at internal/client/codex/live/tcp_proxy.go:304

	return errors.Join(closeErrors...)
}

func newTCPCandidateTunnel(target netip.AddrPort, dialer proxy.ContextDialer, expectedUser, remotePassword string) (*tcpCandidateTunnel, error) {
	if !isPublicProxyTarget(target.Addr()) || target.Port() != 443 {
		return nil, errors.New("Codex live TCP proxy target is not allowed")
	}
	if dialer == nil || strings.TrimSpace(expectedUser) == "" || strings.TrimSpace(remotePassword) == "" {
		return nil, errors.New("Codex live TCP proxy tunnel configuration is incomplete")
	}
	network := "tcp4"
	listenAddress := "127.0.0.1:0"
	if target.Addr().Is6() {
		network = "tcp6"
		listenAddress = "[::1]:0"
	}
	listener, errListen := net.Listen(network, listenAddress)
	if errListen != nil {
		return nil, fmt.Errorf("listen for Codex live TCP proxy candidate: %w", errListen)
	}
	tunnelContext, cancelTunnel := context.WithCancel(context.Background())
	tunnel := &tcpCandidateTunnel{
		listener:        listener,
		target:          target,
		dialer:          dialer,
		expectedUser:    expectedUser,
		remotePassword:  remotePassword,
		connections:     make(map[net.Conn]struct{}),
		validationSlots: make(chan struct{}, maxUnauthenticatedTCPConns),
		ctx:             tunnelContext,
		cancel:          cancelTunnel,
	}
	go tunnel.accept()
	return tunnel, nil
}

func (t *tcpCandidateTunnel) accept() {

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Check 'ulimit -n' and raise the fd limit (each tunnel holds a listener plus accepted conns).
  2. Verify loopback exists in the container ('ip addr show lo'); enable networking in the sandbox.
  3. For IPv6 targets failing on hosts without IPv6, allow the tunnel to fall back to a tcp4 listener carrying an IPv6-mapped target, or disable IPv6 upstream candidates.
  4. Look for listener leaks — tunnels are closed via closeTunnels; confirm error paths actually reach it.
Defensive patterns

Strategy: retry

Validate before calling

// Check loopback availability and fd headroom before starting a session
func canOpenLoopbackListener() bool {
	l, err := net.Listen("tcp4", "127.0.0.1:0")
	if err != nil { return false }
	_ = l.Close()
	return true
}

Try / catch

tunnel, err := startTCPCandidateTunnel(...)
if err != nil {
	if strings.Contains(err.Error(), "listen for Codex live TCP proxy candidate") {
		time.Sleep(100 * time.Millisecond) // transient EMFILE/ENOBUFS
		tunnel, err = startTCPCandidateTunnel(...)
	}
	if err != nil { return err }
}

Prevention

When it happens

Trigger: startTCPCandidateTunnel calls net.Listen("tcp4", "127.0.0.1:0") (or tcp6/[::1]:0) and it returns an error: EMFILE (fd limit reached), ENOBUFS, or no loopback interface in a restricted container/sandbox.

Common situations: ulimit -n too low for many sessions x tunnels; container with network namespace lacking lo; IPv6 disabled so [::1] listen fails for TCP6 targets; heavy session churn leaking listeners.

Related errors


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