kovidgoyal/kitty · error

Failed to open a socket for the remote control file descript

Error message

Failed to open a socket for the remote control file descriptor: %d with error: %w

What it means

do_socket_io was given a remote control file descriptor (fd passthrough mode instead of dialing an address). os.NewFile succeeded but net.FileConn failed, meaning the inherited fd is not a usable socket conn. This typically happens when the fd number is wrong or the descriptor was already consumed/closed.

Source

Thrown at tools/cmd/at/socket_io.go:173

		}
	}
	if io_data.rc.NoResponse {
		return
	}
	return r.read_response_from_conn(conn, io_data.timeout)
}

func do_socket_io(io_data *rc_io_data) (serialized_response []byte, err error) {
	var conn net.Conn
	if global_options.to_network == "fd" {
		fd, _ := strconv.Atoi(global_options.to_address)
		if err != nil {
			return nil, err
		}
		f := os.NewFile(uintptr(fd), "fd:"+global_options.to_address)
		conn, err = net.FileConn(f)
		if err != nil {
			return nil, fmt.Errorf("Failed to open a socket for the remote control file descriptor: %d with error: %w", fd, err)
		}
		defer f.Close()
	} else {
		network := utils.IfElse(global_options.to_network == "ip", "tcp", global_options.to_network)
		conn, err = net.Dial(network, global_options.to_address)
		if err != nil {
			err = fmt.Errorf("Failed to connect to %s:%s with error: %w", network, global_options.to_address, err)
			return
		}
	}
	defer conn.Close()
	return simple_socket_io(&conn, io_data)
}

View on GitHub (pinned to 6d5d0c4406)

Solutions

  1. Verify the fd number being passed matches the FD the supervisor actually allocated (check $LISTEN_FDS, systemd socket files)
  2. Ensure os.NewFile is called exactly once per descriptor; duplicate claims invalidate the fd
  3. Check that the socket unit type matches (stream vs datagram) what FileConn expects
  4. Fall back to explicit net.Dial addressing instead of fd passthrough while debugging

Example fix

// before
f := os.NewFile(uintptr(fd), "fd:"+global_options.to_address)
conn, err = net.FileConn(f)
// after: guard double-claim and verify fd is live
if fd <= 0 { return nil, fmt.Errorf("invalid fd %d", fd) }
f := os.NewFile(uintptr(fd), "fd:"+global_options.to_address)
conn, err = net.FileConn(f)
if err != nil { return nil, fmt.Errorf("fd %d not a socket: %w", fd, err) }
Defensive patterns

Strategy: try-catch

Validate before calling

// Before FileConn: verify the fd refers to a live socket
var st unix.Stat_t
if err := unix.Fstat(fd, &st); err != nil {
    return fmt.Errorf("fd %d unusable: %w", fd, err)
}

Try / catch

conn, err := do_socket_io(...)
if err != nil && strings.Contains(err.Error(), "Failed to open a socket for the remote control file descriptor") {
    // fall back to explicit dial instead of fd passthrough
    conn, err = net.Dial("tcp", explicitAddr)
}

Prevention

When it happens

Trigger: Running the at tool under a supervisor that passes a socket via fd inheritance (e.g. systemd socket activation) when the fd number in global_options doesn't correspond to a live socket, or the Go runtime already took ownership of that fd via another os.NewFile call (double-claim causes 'file already closed' style errors).

Common situations: Misconfigured systemd socket-activation FD name/number, fd offset off-by-one, or code elsewhere in the process calling os.NewFile on the same descriptor first, invalidating it.

Related errors


AI-assisted analysis of kovidgoyal/kitty@6d5d0c4406 (2026-08-27). Data as JSON: /api/errors/3aef7be4bee34c94. Report an issue: GitHub.