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
- Check 'ulimit -n' and raise the fd limit (each tunnel holds a listener plus accepted conns).
- Verify loopback exists in the container ('ip addr show lo'); enable networking in the sandbox.
- 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.
- 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
- Raise RLIMIT_NOFILE in service units (LimitNOFILE=65536) — each tunnel holds listeners and conns.
- Ensure containers have a loopback interface and IPv6 when proxying TCP6 candidates.
- Load-test session churn to confirm listeners are not leaked.
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
- parse upstream WebRTC answer for TCP proxy: %w
- parse upstream WebRTC offer for TCP proxy: %w
- read upstream WebRTC answer ICE credentials: %w
- read upstream WebRTC offer ICE credentials: %w
- upstream WebRTC answer exceeds the %d TCP candidate proxy li
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/1848c001c5538b2a.
Report an issue: GitHub.