diff --git a/emper/io.hpp b/emper/io.hpp
index 64efb93f7d4ea50913c7876bbeb5831eb4c0fa99..b75f31cb1c6fa0143de9622f13e2fe81316c294b 100644
--- a/emper/io.hpp
+++ b/emper/io.hpp
@@ -370,7 +370,7 @@ inline auto writevAndWait(int fildes, const struct iovec *iov, int iovcnt) -> ss
  *
  * @return fd on success, -1 on error
  */
-inline auto openatAndWait(int dirfd, const char *pathname, int flags, mode_t mode = 0) -> size_t {
+inline auto openatAndWait(int dirfd, const char *pathname, int flags, mode_t mode = 0) -> int {
 	OpenatFuture future(dirfd, pathname, flags, mode);
 	future.submit();
 	return future.waitAndSetErrno();
@@ -407,7 +407,7 @@ inline auto openatAndWait(int dirfd, const char *pathname, int flags, mode_t mod
  *
  * @return fd on success, -1 on error
  */
-inline auto openAndWait(const char *pathname, int flags, mode_t mode = 0) -> size_t {
+inline auto openAndWait(const char *pathname, int flags, mode_t mode = 0) -> int {
 	OpenatFuture future(AT_FDCWD, pathname, flags, mode);
 	future.submit();
 	return future.waitAndSetErrno();
@@ -439,7 +439,7 @@ inline auto openAndWait(const char *pathname, int flags, mode_t mode = 0) -> siz
  *
  * @return 0 on success, -1 on error
  */
-inline auto closeAndWait(int fd) -> ssize_t {
+inline auto closeAndWait(int fd) -> int {
 	CloseFuture future(fd);
 	future.submit();
 	return future.waitAndSetErrno();
diff --git a/emper/io/Future.cpp b/emper/io/Future.cpp
index 013997e48ab062918c6ee6572da931c850f92de8..f6767182e3b977a06ef3122b86f590c9ef820c25 100644
--- a/emper/io/Future.cpp
+++ b/emper/io/Future.cpp
@@ -2,8 +2,6 @@
 // Copyright © 2020-2021 Florian Fischer
 #include "Future.hpp"
 
-#include <sys/types.h>	// for ssize_t
-
 #include <cassert>	// for assert
 #include <cerrno>		// for errno, EAGAIN, EWOULDBLOCK
 #include <ostream>	// for operator<<, ostream, basic_ost...
@@ -58,7 +56,7 @@ auto Future::wait() -> int32_t {
 	return returnValue;
 }
 
-auto Future::waitAndSetErrno() -> ssize_t {
+auto Future::waitAndSetErrno() -> int32_t {
 	int32_t res = wait();
 	// move error codes from io_uring into errno
 	if (res < 0) {
diff --git a/emper/io/Future.hpp b/emper/io/Future.hpp
index 09e4128acd47478a65eb328310e2a9b518a2eedf..06f9b45609f8354606a7f4c493f379e1cc56daaf 100644
--- a/emper/io/Future.hpp
+++ b/emper/io/Future.hpp
@@ -4,7 +4,7 @@
 
 #include <liburing.h>		 // for io_uring_prep_read, io_uring_p...
 #include <sys/socket.h>	 // for socklen_t
-#include <sys/types.h>	 // for ssize_t
+#include <sys/types.h>
 
 #include <cassert>
 #include <cstddef>	// for size_t
@@ -82,7 +82,10 @@ class Future : public Logger<LogSubsystem::IO> {
 	const int fd;
 	void* const buf;
 	const size_t len;
-	const int offsetOrFlags;
+	union {
+		const off_t offset;
+		const int flags;
+	};
 
 	/**
 	 * Return value of the operation.
@@ -142,8 +145,11 @@ class Future : public Logger<LogSubsystem::IO> {
 
 	void recordCompletionInternal(Stats& stats, int32_t res) const;
 
-	Future(Operation op, int fd, void* buf, size_t len, int offsetOrFlags)
-			: op(op), fd(fd), buf(buf), len(len), offsetOrFlags(offsetOrFlags){};
+	Future(Operation op, int fd, void* buf, size_t len, off_t offset)
+			: op(op), fd(fd), buf(buf), len(len), offset(offset){};
+
+	Future(Operation op, int fd, void* buf, size_t len, int flags)
+			: op(op), fd(fd), buf(buf), len(len), flags(flags){};
 
  public:
 	// Clang-tidy warns about the exception possibly thrown by
@@ -270,7 +276,7 @@ class Future : public Logger<LogSubsystem::IO> {
 	 *
 	 * @return -1 on error and errno will be set, otherwise the return value of the IO request
 	 */
-	auto waitAndSetErrno() -> ssize_t;
+	auto waitAndSetErrno() -> int32_t;
 
 	/**
 	 * @brief Equivalent to calling wait() after calling submit()
@@ -326,21 +332,28 @@ class PartialCompletableFuture : public Future {
 	 * When the IO request returns to the user the combination of @c future.returnValue and
 	 * @c future.partialCompletion can signal partial completion.
 	 */
-	ssize_t partialCompletion;
+	int32_t partialCompletion;
 
 	// return immediately to the user
-	static const ssize_t DISABLE_PARTIAL_COMPLETION = -1;
+	static const int32_t DISABLE_PARTIAL_COMPLETION = -1;
 	// try to fully complete the request
-	static const ssize_t ENABLE_PARTIAL_COMPLETION = 0;
+	static const int32_t ENABLE_PARTIAL_COMPLETION = 0;
 
-	PartialCompletableFuture(Operation op, int fd, void* buf, size_t len, int offsetOrFlags,
+	PartialCompletableFuture(Operation op, int fd, void* buf, size_t len, off_t offset,
+													 bool completeFully)
+			: Future(op, fd, buf, len, offset),
+				partialCompletion(completeFully ? ENABLE_PARTIAL_COMPLETION : DISABLE_PARTIAL_COMPLETION){};
+	PartialCompletableFuture(Operation op, int fd, void* buf, size_t len, int flags,
 													 bool completeFully)
-			: Future(op, fd, buf, len, offsetOrFlags),
+			: Future(op, fd, buf, len, flags),
 				partialCompletion(completeFully ? ENABLE_PARTIAL_COMPLETION : DISABLE_PARTIAL_COMPLETION){};
 
-	PartialCompletableFuture(Operation op, int fd, void* buf, size_t len, int offsetOrFlags,
-													 ssize_t partialCompletion)
-			: Future(op, fd, buf, len, offsetOrFlags), partialCompletion(partialCompletion){};
+	PartialCompletableFuture(Operation op, int fd, void* buf, size_t len, off_t offset,
+													 int32_t partialCompletion)
+			: Future(op, fd, buf, len, offset), partialCompletion(partialCompletion){};
+	PartialCompletableFuture(Operation op, int fd, void* buf, size_t len, int flags,
+													 int32_t partialCompletion)
+			: Future(op, fd, buf, len, flags), partialCompletion(partialCompletion){};
 
 	/**
 	 * Used for Stats::recordCompletion double dispatch
@@ -414,7 +427,7 @@ class PartialCompletableFuture : public Future {
 	 *
 	 * @return return the result received from the io_uring
 	 */
-	auto wait(ssize_t& partialCompletion) -> int32_t {
+	auto wait(int32_t& partialCompletion) -> int32_t {
 		int32_t res = Future::wait();
 		partialCompletion = this->partialCompletion;
 		return res;
@@ -424,7 +437,7 @@ class PartialCompletableFuture : public Future {
 	/*
 	 * @brief Equivalent to calling wait(partialCompletion) after calling submit()
 	 */
-	inline auto submitAndWait(ssize_t& partialCompletion) -> int32_t {
+	inline auto submitAndWait(int32_t& partialCompletion) -> int32_t {
 		Future::submit();
 		return wait(partialCompletion);
 	}
@@ -445,15 +458,14 @@ class PartialCompletableFuture : public Future {
 class SendFuture : public PartialCompletableFuture {
 	void prepareSqe(io_uring_sqe* sqe) override {
 		if (partialCompletion == DISABLE_PARTIAL_COMPLETION) {
-			io_uring_prep_send(sqe, fd, buf, len, offsetOrFlags);
+			io_uring_prep_send(sqe, fd, buf, len, flags);
 		} else {
-			io_uring_prep_send(sqe, fd, (char*)buf + partialCompletion, len - partialCompletion,
-												 offsetOrFlags);
+			io_uring_prep_send(sqe, fd, (char*)buf + partialCompletion, len - partialCompletion, flags);
 		}
 	}
 
  public:
-	SendFuture(int socket, const void* buffer, size_t length, int flags, ssize_t partialCompletion)
+	SendFuture(int socket, const void* buffer, size_t length, int flags, int32_t partialCompletion)
 			: PartialCompletableFuture(Operation::SEND, socket, const_cast<void*>(buffer), length, flags,
 																 partialCompletion){};
 
@@ -463,9 +475,7 @@ class SendFuture : public PartialCompletableFuture {
 };
 
 class RecvFuture : public Future {
-	void prepareSqe(io_uring_sqe* sqe) override {
-		io_uring_prep_recv(sqe, fd, buf, len, offsetOrFlags);
-	}
+	void prepareSqe(io_uring_sqe* sqe) override { io_uring_prep_recv(sqe, fd, buf, len, flags); }
 
  public:
 	RecvFuture(int socket, void* buffer, size_t length, int flags)
@@ -484,6 +494,7 @@ class ConnectFuture : public Future {
 
 class AcceptFuture : public Future {
 	void prepareSqe(io_uring_sqe* sqe) override {
+		// NOLINTNEXTLINE(performance-no-int-to-ptr)
 		io_uring_prep_accept(sqe, fd, (struct sockaddr*)buf, (socklen_t*)len, 0);
 	}
 
@@ -495,15 +506,15 @@ class AcceptFuture : public Future {
 class ReadFuture : public PartialCompletableFuture {
 	void prepareSqe(io_uring_sqe* sqe) override {
 		if (partialCompletion == DISABLE_PARTIAL_COMPLETION) {
-			io_uring_prep_read(sqe, fd, buf, len, offsetOrFlags);
+			io_uring_prep_read(sqe, fd, buf, len, offset);
 		} else {
 			io_uring_prep_read(sqe, fd, (char*)buf + partialCompletion, len - partialCompletion,
-												 offsetOrFlags + partialCompletion);
+												 offset + partialCompletion);
 		}
 	}
 
  public:
-	ReadFuture(int fildes, void* buf, size_t nbyte, int offset, bool read_all = false)
+	ReadFuture(int fildes, void* buf, size_t nbyte, off_t offset, bool read_all = false)
 			: PartialCompletableFuture(
 						Operation::READ, fildes, buf, nbyte, offset,
 						read_all ? ENABLE_PARTIAL_COMPLETION : DISABLE_PARTIAL_COMPLETION){};
@@ -511,28 +522,30 @@ class ReadFuture : public PartialCompletableFuture {
 
 class OpenatFuture : public Future {
 	void prepareSqe(io_uring_sqe* sqe) override {
-		io_uring_prep_openat(sqe, fd, reinterpret_cast<const char*>(buf), len, offsetOrFlags);
+		io_uring_prep_openat(sqe, fd, reinterpret_cast<const char*>(buf), static_cast<int>(len),
+												 static_cast<mode_t>(offset));
 	}
 
  public:
 	OpenatFuture(int dirfd, const void* pathname, int flags, mode_t mode = 0)
-			: Future(Operation::OPENAT, dirfd, const_cast<void*>(pathname), flags, mode){};
+			: Future(Operation::OPENAT, dirfd, const_cast<void*>(pathname), flags,
+							 static_cast<off_t>(mode)){};
 };
 
 class WriteFuture : public PartialCompletableFuture {
 	void prepareSqe(io_uring_sqe* sqe) override {
 		if (partialCompletion == DISABLE_PARTIAL_COMPLETION) {
-			io_uring_prep_write(sqe, fd, buf, len, offsetOrFlags);
+			io_uring_prep_write(sqe, fd, buf, len, offset);
 		} else {
 			// TODO: think about partial writes with incremental offset!
 			// Are transparent incremental writes on files without O_APPEND even reasonable?
 			io_uring_prep_write(sqe, fd, (char*)buf + partialCompletion, len - partialCompletion,
-													offsetOrFlags + partialCompletion);
+													offset + partialCompletion);
 		}
 	}
 
  public:
-	WriteFuture(int fildes, const void* buf, size_t nbyte, int offset, bool write_all = true)
+	WriteFuture(int fildes, const void* buf, size_t nbyte, off_t offset, bool write_all = true)
 			: PartialCompletableFuture(
 						Operation::WRITE, fildes, const_cast<void*>(buf), nbyte, offset,
 						write_all ? ENABLE_PARTIAL_COMPLETION : DISABLE_PARTIAL_COMPLETION){};
diff --git a/emper/io/GlobalIoContext.cpp b/emper/io/GlobalIoContext.cpp
index da8c944d7d1ecc18cecbf498a0ab226a3d81091e..e7bb9982a6c574a8c14d7b185e53cdf21a524682 100644
--- a/emper/io/GlobalIoContext.cpp
+++ b/emper/io/GlobalIoContext.cpp
@@ -123,7 +123,7 @@ auto GlobalIoContext::globalCompleterFunc(void* arg) -> void* {
 			case PointerTags::Future: {
 				auto* future = tptr.getPtr<Future>();
 
-				uint32_t res = cqe->res;
+				int32_t res = cqe->res;
 
 				future->recordCompletion(globalIoContext->stats, res);
 				future->completeFromAnywhere(res);
@@ -152,7 +152,7 @@ void GlobalIoContext::startGlobalCompleter() {
 
 void GlobalIoContext::initiateTermination() {
 	uint64_t write_buf = 1;
-	int bytes_written = write(notificationEventFd, &write_buf, sizeof(write_buf));
+	ssize_t bytes_written = write(notificationEventFd, &write_buf, sizeof(write_buf));
 	if (bytes_written != sizeof(write_buf)) {
 		DIE_MSG_ERRNO("Writing to globalIo termination eventfd failed");
 	}
diff --git a/emper/lib/TaggedPtr.hpp b/emper/lib/TaggedPtr.hpp
index fcbeb1d3721b3593e273616755fa9cc86e1a9eb7..532082c20730df4a9a2cd8b7a4ad985013f33a4e 100644
--- a/emper/lib/TaggedPtr.hpp
+++ b/emper/lib/TaggedPtr.hpp
@@ -41,6 +41,7 @@ class TaggedPtr {
 	template <typename T>
 	[[nodiscard]] inline auto getPtr() const -> T* {
 		// ignore the least significant bit of the tagged pointer
+		// NOLINTNEXTLINE(performance-no-int-to-ptr)
 		return reinterpret_cast<T*>(tptr & (TPTR_POINTER_MASK - 1));
 	}
 
@@ -91,6 +92,7 @@ class TaggedPtr {
 
 	inline operator uintptr_t() const { return tptr; }
 
+	// NOLINTNEXTLINE(performance-no-int-to-ptr)
 	inline operator void*() const { return reinterpret_cast<void*>(tptr); }
 
 	inline operator bool() const { return tptr != 0; }
diff --git a/tests/io/SimpleDiskAndNetworkTest.cpp b/tests/io/SimpleDiskAndNetworkTest.cpp
index 182bcdbfce52463de6d2850aaa299f87e51131e6..919c2bf72325f1f9b4a8065e9277a6036ad90370 100644
--- a/tests/io/SimpleDiskAndNetworkTest.cpp
+++ b/tests/io/SimpleDiskAndNetworkTest.cpp
@@ -26,9 +26,8 @@
 static void server_func(int sockfd) {
 	struct sockaddr_in clientaddr;
 	socklen_t clientaddr_len = sizeof(clientaddr);
-	auto client_fd =
-			emper::io::accept(sockfd, reinterpret_cast<struct sockaddr*>(&clientaddr), &clientaddr_len)
-					->wait();
+	auto client_fd = emper::io::acceptAndWait(sockfd, reinterpret_cast<struct sockaddr*>(&clientaddr),
+																						&clientaddr_len);
 
 	if (client_fd < 0) {
 		DIE_MSG_ERRNO("accept failed");
@@ -40,7 +39,7 @@ static void server_func(int sockfd) {
 	char read_buf[MAX];
 
 	for (;;) {
-		int received = emper::io::recv(client_fd, recv_buf, sizeof(recv_buf), 0)->wait();
+		ssize_t received = emper::io::recvAndWait(client_fd, recv_buf, sizeof(recv_buf), 0);
 		if (received == 0) {
 			exit(EXIT_SUCCESS);
 		}
@@ -57,7 +56,7 @@ static void server_func(int sockfd) {
 			DIE_MSG_ERRNO("mkstemp failed");
 		}
 
-		int written = emper::io::writeFile(file_fd, recv_buf, received)->wait();
+		ssize_t written = emper::io::writeFileAndWait(file_fd, recv_buf, received);
 		if (written < 0) {
 			DIE_MSG_ERRNO("write failed");
 		}
@@ -68,7 +67,7 @@ static void server_func(int sockfd) {
 			DIE_MSG_ERRNO("open failed");
 		}
 
-		int bytes_read = emper::io::readFile(file_fd, read_buf, written)->wait();
+		ssize_t bytes_read = emper::io::readFileAndWait(file_fd, read_buf, written);
 		if (bytes_read == 0) {
 			DIE_MSG("nothing to read");
 		}
@@ -78,7 +77,7 @@ static void server_func(int sockfd) {
 		}
 		close(file_fd);
 
-		int sent = emper::io::send(client_fd, read_buf, bytes_read, 0)->wait();
+		ssize_t sent = emper::io::sendAndWait(client_fd, read_buf, bytes_read, 0);
 		if (sent == 0) {
 			DIE_MSG("client socket unexpected shutdown");
 		}
@@ -107,7 +106,7 @@ static void server_func(int sockfd) {
 		iov[1].iov_len = s2.length();
 
 		auto writevFuture = emper::io::writev(file_fd, &iov[0], iovcnt);
-		written = writevFuture->wait();
+		written = writevFuture->waitAndSetErrno();
 		if (written < 0) {
 			DIE_MSG_ERRNO("wrtev failed");
 		}
@@ -119,7 +118,7 @@ static void server_func(int sockfd) {
 		}
 
 		auto readFuture = emper::io::readFile(file_fd, read_buf, written, 0, true);
-		bytes_read = readFuture->wait();
+		bytes_read = readFuture->waitAndSetErrno();
 		if (bytes_read == 0) {
 			DIE_MSG("nothing to read");
 		}