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
- Verify the fd number being passed matches the FD the supervisor actually allocated (check $LISTEN_FDS, systemd socket files)
- Ensure os.NewFile is called exactly once per descriptor; duplicate claims invalidate the fd
- Check that the socket unit type matches (stream vs datagram) what FileConn expects
- 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
- Claim each inherited fd with os.NewFile exactly once
- Validate supervisor fd numbers (LISTEN_FDS / socket units) in smoke tests
- Keep an explicit-address fallback path for environments without fd passing
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
- Not a valid file descriptor number: %#v. Cannot use: %s
- This must be run as kitten choose-files
- Pipe to kitten was broken while sending data to it
- Unknown action: {action}
- Too much piped data
AI-assisted analysis of kovidgoyal/kitty@6d5d0c4406 (2026-08-27).
Data as JSON: /api/errors/3aef7be4bee34c94.
Report an issue: GitHub.